这是我参与「第五届青训营 」伴学笔记创作活动的第5天
1.高质量编程
1.1高质量简介
高质量-编写的代码达到正确可靠、简洁清晰的目标
编程原则:
- 简单性,消除多余复杂性
- 可读性,确保代码可读是编写可维护代码的条件
- 生产力,提高团队工作效率
1.2编码规范
编码规范包括以下几个内容:
- 代码格式
- 注释
- 命名规范
- 控制流程
- 错误和异常处理
1.2.1编码规范-代码格式
- gofmt:Go语言官方提供工具,可以自动格式化Go语言代码为官方统一风格
- goimports:Go语言官方提供工具(=gofmt+依赖包管理),自动删除依赖包的引用、将依赖包按字母排序并分类
1.2.2编码规范-注释
注释作用:
- 解释代码作用
- 解释代码如何做
- 解释代码实现原因
- 解释代码什么情况会出错
1.3命名规范
- 缩略词要大写,但是当它位于变量开头并不需要导出时,使用全小写
- 用ServerHTTP而不是ServerHttp
- 用XMLHTTPRequest或者xmlHTTPRequest
- 变量距离其被使用的地方越远,则需要携带更多的上下文信息
- function
- 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
- 函数名要尽量简洁
- 当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义
- 当名为foo的包某个函数返回类型T时 (T并不是Foo),可以在函数名中加入类型信息
- package
- 只由小写字母组成。不包含大写字母和下划线等字符
- 简短并包含一定的上下文信息。例如schema、task等
- 不要与标准库同名。例如不要使用sync或strings
- 不使用常用变量名作为包名。例如使用bufio而不是buf
- 使用单数而不是复数。例如使用encoding而不是encodings
- 谨慎地使用缩写。例如使用fmt在不破坏上下文的情况下比format更加简短
1.4控制流程
- 避免嵌套,保持正常流程清晰
如果if-else两个分支中都包含return语句,则可以去除冗余的else
- 尽量保持正常代码路径为最小缩进
优先处理错误情况/特殊情况,尽早返回或继续循环来减少嵌套
1.5错误和异常处理
- 简单错误
- 简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误
- 优先使用errors.New来创建匿名变量来直接表示简单错误
- 如果有格式化的需求,使用fmt.Errorf
- 错误的Wrap和Unwrap
- 错误的Wrap实际上是提供了一个error嵌套另一error的能力,从而生成一个error的跟踪链
- 在fmt.Errorf中使用%w关键字来将一个错误关联至错误链中
- 错误判定
- 判定一个错误是否为特定错误,使用errors.Is
- 不同于使用==,使用该方法可以判定错误链上的所有错误是否含有特定的错误
- 在错误链上获取特定种类的错误,使用errors.As
- panic
- 不建议在业务代码中使用panic
- 调用函数不包含recover会造成程序崩溃
- 若问题可以被屏蔽或解决,建议使用error代替panic
- 当程序启动阶段发生不可逆转的错误时可以在init或main函数中使用panic
- recover
- recover只能在被defer的函数中使用
- 嵌套无法生效
- 只在当前goroutine生效
- defer的语句是后进先出
- 如果需要更多的上下文信息,可以recover后在log中记录当前的调用栈
1.6性能优化建议
1.6.1性能优化建议-Benchmark
- 性能的好坏需要用实际的数据量来衡量
- Go语言中提供了支持基准性能测试的benchmark工具
使用命令go test -bench=. -benchmem可以进行评估程序的性能,进入到对应的目录,打开cmd运行该命令,运行结果如下
插语: 当我运行该命令
go test -bench=. -benchmem的时候,出现了如下图的错误
解决办法:
通过
go env命令查看发现GO111MODULE没有关闭
我们通过set GO111MODULE=off命令将它关闭,问题就解决了
1.6.2性能优化建议-Slice
- slice预分配内存
尽量在使用make()初始化切片时提供容量信息,特别是在追加切片时,指定容量信息后,性能会提高
1.6.3性能优化建议-map
- map预分配内存 初始化map时提供容量信息,指定容量信息后,性能会提高
1.6.4性能优化建议-字符串处理
- 字符串三种拼接方式
- 直接使用加号+
- strings.Builder
- bytes.Buffer 推荐使用strings.Builder进行拼接字符串,因为它最快,其中bytes.Buffer较快,直接使用加号+最慢
1.6.5性能优化建议-空结构体
空结构体struct{}实例不占据任何内存空间,可作为占位符使用,可用来实现set(Go中不支持set实现),在集合场景下,只需要用到map的键而不使用值
1.6.6性能优化建议-atomic包
多线程编程中怎么保证线程安全,可以使用atomic包和锁,锁的实现是通过操作系统来实现,属于系统调用,atomic 操作是通过硬件实现的,效率比锁高很多
2.性能调优实战
2.1性能调优原则
- 以数据为依据而不是猜测(因为实际代码运行环境可能和预想不一样)
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化(系统一直在迭代,况且系统早期用户不多,可以等系统出现性能问题在做优化)
- 不要过度优化
2.2性能分析工具-pprof工具
2.2.1pprof工具概念
pprof是用于可视化和分析性能分析数据的工具(我们希望知道应用在什么地方耗费了多少 CPU、Memory)
2.2.2pprof工具-排查实战
- 启动如下图程序
- 启动完成后在本地浏览器(http://localhost:6060/debug/pprof/) 查看性能指标
- CPU性能
使用命令—go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
在命令行窗口进行分析,如下图
继续输入top命令,如下图
- flat 当前函数本身的执行耗时
- flat% flat占CPU总时间的比例
- sum% 上面每一行的flat%总和
- cum 指当前函数本身加上其调用函数的总耗时
- cum% cum占CPU总时间的比例
- 当Flat=Cum时,意思是函数中没有调用其他函数,当Flat=0时,意思是函数中只有其他函数的调用
使用list+函数名命令可以查看具体问题代码,如下图(可以看到for循环所占时间最多)
- Heap-堆内存
使用命令——go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap" ,出现如下图问题(说明没有安装Graphviz并没有配置环境变量)
解决问题: 下载Graphviz,安装过程中选择如下图的添加系统PATH
安装好以后Graphviz,继续在终端中使用命令go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap" ,如下图所示(下图说明mouse程序最占内存)
- goroutine-协程
goroutine泄露也会导致内存泄露,使用——go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine" 命令查看
我们还可以选择使用火焰图进行分析性能,如下图
- 由上到下表示调用顺序
- 每一块代表一个函数,越长代表占用 CPU 的时间更长
- 火焰图是动态的,支持点击块进行分析
- mutex-锁
使用命令——go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
- block-阻塞
使用命令——go tool pprof -http=:8080 "[http://localhost:6060/debug/pprof/block"