这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天
高质量编程与调优
高质量编程
什么是高质量编程
编写的代码能够达到正确可靠,简洁清晰的目标可称为高质量代码
- 各种边界考虑完备
- 各种异常情况处理完善
- 易读易完善
编程原则
- 简单性:以简单清晰的逻辑编写代码,不理解的代码无法修复改进
- 可读性:编写可维护的代码的第一步是确保代码可读
编码规范(如何编写高质量的go代码)
- 代码格式
- 注释
- 命名规范
- 控制流程
- 错误和异常处理
编码规范-代码格式
推荐使用gofmt自动格式化代码,保证了所有的go代码和官方推荐的格式一致,而且可以很方便的进行配置。在goland中可以开启此功能。
代码的规范性在团队开发的过程中十分重要,可以减少冲突,团队合作的过程中review其他人的代码时就能体会到代码规范的重要性
注释
注释应该解释代码的作用,代码是如何实现此功能的,这样实现的原因,什么情况会出错
包中声明的每个公共的符号(变量,常量,函数以及结构都需要添加注释),任何既不明显也不简短的公共功能必须予以注释。无论长度或复杂程度如何,对库中的任何函数都必须进行注释
但,实现接口的方法不需要写注释
命名规范
- 简洁胜于冗长
- 缩略词全大写,但当位于变量开头且不需要导出时,使用全小写
- 变量角力其被使用的地方越远,则需要携带越多的下文信息
- 函数名尽量简短,函数名不携带包名的上下文信息,因为包名和函数名总是成对出现
- 包名只能由小写字母在组成,不包含大写字母和下划线等字符,不要与标准库同名,一定要简短并包含一定的上下文信息
控制流程的规范
- 避免嵌套,保持正常流程清晰,处理逻辑尽量走直线
- 正常流程代码沿着屏幕向下移动
- 故障问题大多出现在复杂的条件语句和循环语句中
错误和异常的处理
- 优先使用errors.New来创建匿名变量来直接表示简单错误
- 如果有格式化的需求,使用fmt.Errorf
- 错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
- 在fmt.Errorf中使用: %w关键字来将一个错误关联至错误链中
- 使用errors.Is来判断某一个错误是否为某种特定的错误,而不是用==。使用erros.As来判断某一个错误是否为某种类型的错误
panic
- 不建议在业务代码中使用panic
- 调用函数不包含recover会造成程序崩溃
- 若问题可以被屏蔽或解决,建议使用error代替panic
- 当程序启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic
defer
defer语句会在函数返回前调用 多个defer语句是后进先出
func main() {
if true {
defer fmt.Printf("1")
} else {
defer fmt.Printf("2")
}
defer fmt.Printf("3")
}
// 输出31
性能优化建议
性能优化的前提是代码能够满足正确,可靠,易懂易维护
性能优化是综合评估,有时候时间效率和空间效率可能对立,此时需要分析哪个更重要,做出适当的折中,例如多花费一些内存来提高性能。
性能优化建议-Benchmark
测试性能需要实际数据,go语言提供了支持基准性能测试的benchmark工具
以计算斐波拉契数列的函数为例,分两个文件,fib.go编写 函数代码,fib._test.go编写benchmark的逻辑,通过命令运行benchmark可以得到测试结果benchmem表示也统计内存信息
slice性能优化建议
slice是go中最常用的结构,也很方便,那么在使用过程中有哪些点需要注意几点:
- 预分配,尽可能在使用make()初始化切片时提供容量信息,特别是在追加切片时对比看下两种情况的性能表现,左边是没有提供初始化容量信息,右边是设置了容量大小。结果中可以看出执行时间相差很多,预分配只有一次内存分配。与Java中很多集合的数据结构相似,如果我们能够在初始化slice时,做到给予一个合适的大小,那么在后续运行的过程中可以避免再分配一个大内存,进行数据的copy
- 另一个陷阱是大内存:原切片较大,代码在原切片基础上新建小切片。原底层数组在内存中有引用,得不到释放
map性能优化建议
与slice相似,同时也和Java中map的性能优化相同,如果能预知业务代码中map存放的上限,我们可以在初始化时设定map的容量
字符串相关的性能优化建议
在字符串拼接的业务中,使用+拼接性能最低,strings.Builder, bytes.Buffer 相近,strings.Buffer 更快
字符串在go语言中是不可变类型,占用内存大小是固定的。使用+每次都会重新分配内存,来存放拼接后的字符串。
strings.Builder, bytes.Buffer底层都是[]byte 数组
bytes.Buffer转化为字符串时重新申请了一块空间strings.Builder直接将底层的[]byte转换成了字符串类型返回
空结构体|性能优化建议
空结构体的实例不占据任何的内存空间,可作为各种场景下的占位符使用
atomic包|性能优化建议
对于多线程编程的场景,使用atomic这种原子操作要比sync.Mutex锁要优
性能优化实战
性能调优的原则:
- 要依靠数据而不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
搭建
main.go中初始化http服务和pprof接口的代码
go func() {
if err := http.ListenAndServe(":6060", nil); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
在浏览器中打开http://localhost:6060/debug/pprof