高质量编程与调优|青训营笔记

47 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天

高质量编程与调优

高质量编程

什么是高质量编程

编写的代码能够达到正确可靠,简洁清晰的目标可称为高质量代码

  1. 各种边界考虑完备
  2. 各种异常情况处理完善
  3. 易读易完善

编程原则

  1. 简单性:以简单清晰的逻辑编写代码,不理解的代码无法修复改进
  2. 可读性:编写可维护的代码的第一步是确保代码可读

编码规范(如何编写高质量的go代码)

  1. 代码格式
  2. 注释
  3. 命名规范
  4. 控制流程
  5. 错误和异常处理

编码规范-代码格式

推荐使用gofmt自动格式化代码,保证了所有的go代码和官方推荐的格式一致,而且可以很方便的进行配置。在goland中可以开启此功能。
代码的规范性在团队开发的过程中十分重要,可以减少冲突,团队合作的过程中review其他人的代码时就能体会到代码规范的重要性

注释

注释应该解释代码的作用,代码是如何实现此功能的,这样实现的原因,什么情况会出错
包中声明的每个公共的符号(变量,常量,函数以及结构都需要添加注释),任何既不明显也不简短的公共功能必须予以注释。无论长度或复杂程度如何,对库中的任何函数都必须进行注释
但,实现接口的方法不需要写注释

命名规范

  1. 简洁胜于冗长
  2. 缩略词全大写,但当位于变量开头且不需要导出时,使用全小写
  3. 变量角力其被使用的地方越远,则需要携带越多的下文信息
  4. 函数名尽量简短,函数名不携带包名的上下文信息,因为包名和函数名总是成对出现
  5. 包名只能由小写字母在组成,不包含大写字母和下划线等字符,不要与标准库同名,一定要简短并包含一定的上下文信息

控制流程的规范

  1. 避免嵌套,保持正常流程清晰,处理逻辑尽量走直线
  2. 正常流程代码沿着屏幕向下移动
  3. 故障问题大多出现在复杂的条件语句和循环语句中

错误和异常的处理

  1. 优先使用errors.New来创建匿名变量来直接表示简单错误
  2. 如果有格式化的需求,使用fmt.Errorf
  3. 错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
  4. 在fmt.Errorf中使用: %w关键字来将一个错误关联至错误链中
  5. 使用errors.Is来判断某一个错误是否为某种特定的错误,而不是用==。使用erros.As来判断某一个错误是否为某种类型的错误

panic

  1. 不建议在业务代码中使用panic
  2. 调用函数不包含recover会造成程序崩溃
  3. 若问题可以被屏蔽或解决,建议使用error代替panic
  4. 当程序启动阶段发生不可逆转的错误时,可以在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中最常用的结构,也很方便,那么在使用过程中有哪些点需要注意几点:

  1. 预分配,尽可能在使用make()初始化切片时提供容量信息,特别是在追加切片时对比看下两种情况的性能表现,左边是没有提供初始化容量信息,右边是设置了容量大小。结果中可以看出执行时间相差很多,预分配只有一次内存分配。与Java中很多集合的数据结构相似,如果我们能够在初始化slice时,做到给予一个合适的大小,那么在后续运行的过程中可以避免再分配一个大内存,进行数据的copy
  2. 另一个陷阱是大内存:原切片较大,代码在原切片基础上新建小切片。原底层数组在内存中有引用,得不到释放

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锁要优

性能优化实战

性能调优的原则:

  1. 要依靠数据而不是猜测
  2. 要定位最大瓶颈而不是细枝末节
  3. 不要过早优化
  4. 不要过度优化

搭建

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

引用:

mp.weixin.qq.com/s/boJff71vC…