这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
高质量编程和性能调优实战
课程任务
- 如何编写更简洁清晰代码
- 常用go程序优化手段
- 熟悉go性能优化工具
- 了解工程中性能优化的原则和流程
1 高质量编程代码
应该满足下面几个条件:
-
代码正确可靠、简洁清晰
-
各种边界条件是否考虑完备
-
异常情况处理,稳定性保证
-
易读易维护
1.1 代码规范
Go语言官方提供的工具: gofmt自动格式化工具,将代码自动格式化为官方统一风格 goimports工具,相当于gofmt加上依赖包管理,可以自动增删依赖包
注释规范
关于注释的规范,使用注释的四个场合:
- 解释代码的作用:适合注释公共符号
- 解释代码如何做的:适合注释实现过程
- 解释代码实现原因:适合注释代码的外部因素,提供额外的上下文信息
- 解释代码什么情况下会出错:适合注释代码的限制条件
要注意的是:对于任何既不简短也不明显的公共代码必须加上注释;代码是最好的注释,注释应该提供代码未表达出的上下文信息 我回想起在我初学编程时,总会在各种地方添加注释,比如变量的定义、业务代码中等等,注释很大地帮助了我去理顺自己的思路和理解先前写的代码,不至于写到后面时看着代码像看着一堆看不懂的英文一样;但今天的课上完之后,我发现先前一些加的注释实际上并没有提供更多的信息,我觉得更好的做法应该是在编写代码时就注意更好地表达,不能随便对方法和变量起有歧义的名字,这就刚好引出了下面的命名规范。
命名规范
变量名
- 简洁胜于冗长
- 变量距离使用的地方越远,越需要携带更多的上下文信息(名字需要更多的上下文信息)
函数名
- 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现
- 函数名尽量简短
包名
-
只有小写字母,不包含大写字母和下划线
-
简短,包含上下文信息
-
不要与标准库重名
-
尽量满足:不使用常用变量名作为包名;尽量用单数不用复数;谨慎使用缩写
小结:核心是降低阅读和理解代码成本,重点考虑上下文信息,设计简洁清晰的名字。
控制流程
避免嵌套,保持正常流程清晰
- 如果两个分支(if else)都包含return,就应当去除冗余的else
尽量保持正常代码路径为最小缩进
- 对于异常情况或错误情况尽早返回来避免嵌套
小结:线性原理,处理逻辑尽量走直线,避免复杂嵌套分支;正常流程代码应该要沿着屏幕向下移动;提升代码可维护性和可读性;故障问题大多出现在复杂条件语句和循环语句中
错误和异常处理
简单错误
- 仅出现一次
- 优先使用error.New来创建匿名变量直接表示简单错误
- 如果有格式化需求,使用fmt.Error
错误的Wrap和Unwrap
- 生成error的跟踪链
- 在fmt.Error中使用%w关键字来将一个错误关联至错误链中
错误判定
- errors.Is可以判定错误链上的所有错误是否含有特定的错误
- error.As获取特定种类的错误
Panic
Recover
- Recover只能在被defer的函数中使用
- 嵌套无法生效
- 只在当前goroutine生效
- 如果需要更多的上下文信息,可以recover后在log中记录当前的调用栈
小结:error尽可能提供简明的上下文信息链,方便定位问题;panic用于真正异常的情况; Recover生效范围:在当前goroutine的被defer函数中
性能优化建议
- 使用Benchmark,go提供了支持基准性能测试的Benchmark工具
- Slice预分配内存
-
尽可能在初始化切片时提供容量信息 -
如果原切片较大,在原切片上新建小切片,原底层数组在内存中有引用,就得不到释放,就可以使用copy代替re-slice
- Map预分配内存
- 使用string.Builder来拼接字符串
- 空结构体实例不占据任何空间,可在任何情况下作为占位符使用
- atomin包 锁的实现通过操作系统实现,属于系统调用;atomic操作是通过硬件实现,效率比锁高
小结:避免常见的性能陷阱可以保证大部分程序的性能;普通的应用代码,不要一味追求性能;越高级的性能优化手段越容易出问题;在准确可靠简洁清晰前提下提升性能
性能调优实战
性能调优原则:
-
要依靠数据而不是猜测
-
要定位最大瓶颈而不是细枝末节
-
不要过早优化
-
不要过度优化
性能分析工具pprof 用于可视化和分析性能分析数据的工具