Go语言高质量编程 | 青训营

83 阅读5分钟

高质量编程

编程原则:

  • 简单性:以简单逻辑写代码(让团队队友阅读容易)
  • 可读性:编写可维护代码的第一步是确保代码可读,代码是写给人看的
  • 生产力:团队整体工作效率非常重要

代码编码规范:

公共符号必须要注释;

  • 包含声明的每个公共符号,常量,变量,函数,结构等都要添加注释;
  • 任何既不明显也不简短的公共功能必须注释;
  • 无论长度或者复杂程度,库中的任何函数都要注释 例外:不需要注释实现接口的方法

代码格式规范:推荐用gofmt可以自动格式化代码

  • gofmt是go语言官方提供的工具,能自动格式化go语言代码为官方统一风格
  • goimports:go语言官方提供的工具,实际等于gofmt加上依赖包管理,自动增删依赖包的引用,将依赖包按字母排序并分类

注释规范:

  1. 注释解释代码作用(注释公共符号)
  2. 注释应该解释代码是如何做的(注释代码实现过程,如for循环)
  3. 注释应该解释代码实现的原因(解释代码的外部因素,对脱离上下文难以解释的代码进行注释,相当于提供额外上下文)
  4. 注释应该解释代码什么情况下会出错(解释代码的限制条件,什么情况下出错)
  5. 公共符号始终都要注释(尽管LimitedReader.Read本身没有注释,但他紧跟LimitedReader结构的声明,明确它的作用)

命名规范(variable):

  1. 简介胜于冗长
  2. 缩略词全大写,但是当其位于变量开头且不需要导出,全部使用小写
  3. 变量距离被使用的地方越远,则需要携带越多的上下文信息,全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易便认出其含义
  4. 变量名如果可以准确表现含义,就没必要让变量名变简洁
  5. 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
  6. 函数名尽量简短
  7. 当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义(返回类型和包名一致)
  8. 当名为foo的包某个函数返回类型T时(T不是Foo),可以在函数名中加入类型信息
  9. 包名(package):只由小写字母组成,不包含大写字母和下划线等字符--简短并包含一定的上下文信息(例如schema)--不要与标准库同名,例如strings

以下规则尽量满足: 不使用常用变量作为包名,使用单数而不是复数(encoding而不是encodings),谨慎使用缩写,例如fmt在不破坏上下文情况下用format更简洁

控制流程:

  1. 避免嵌套,保持正常流程清晰(如果两个分支都包含return,可以去除多余的else)
  2. 尽量保持正常代码路径为最小缩进(优先处理错误/特殊情况,今早返回或继续循环减少嵌套)
  3. 简单错误:指仅仅出现一次的错误,且在其他地方不需要捕获该错误;优先使用errors.New来创建匿名变量直接表示简单错误;如有格式化的需求,使用fmt.Errorf

错误和异常处理:

  1. 错误的Wrap和Unwrap:错误的wrap实际上提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链;在fmt.Errorf中使用%w关键字来将一个错误关联到错误链中
  2. 错误判定:判定是否特定错误,使用该方法可以判定错误链上的所有错误是否含有特定的错误;
  3. 在错误链上获取特定种类的错误,使用errors.As
  4. panic:不建议在业务代码中使用;调用函数不包含recover会造成程序崩溃;若问题可以被屏蔽或解决,建议用error代替panic;
  5. 程序启动阶段发生不可逆转的错误,可以在Init或者main函数中使用panic(尽早暴露错误)
  6. recover:只能在被defer的函数中使用;嵌套无法生效;只在当前goroutine生效;defer的语句先进后出;如果需要更多的上下文信息,可以recover后在log中记录当前调用栈

关于性能优化

下面提供几个性能优化方面的建议:

go语言提供了支持基准性能测试的benchmark工具:

go tese-bench=. -benchman

性能表现要用实际数据衡量

Z`BQOSCPEQ2HE26DMI9SK.png

slice预分配内存:

  1. 使用make()初始化切片时提供内存容量信息;
  2. 切片本质时一个数组片段的描述,包括数组指针,片段长度,片段容量(不改变内存分配情况下的最大长度);
  3. 切片并不复制切片指向的元素;
  4. 创建一个新的切片会复用原来切片的底层数组;
  5. 底层数组会在超过容量时扩容,会影响时间,所以尽量在初始化时设置好值;

slice另一个陷阱:大内存未释放:在已有切片基础上创建切片,不会创建新的底层数组,那原来的底层数组在内存中的引用得不到释放。

解决办法: 用copy代替re-slice

map

map预分配内存:map可以自动扩容

字符串处理

  • 使用string.Builder拼接字符串,性能最差,拼接时不重新分配内存
  • 用string.Buffer更快,他会重新申请空间

使用空结构体节省内存:

  • 空结构体struct{}实例不占用任何内存,可以做占位符,节省资源
  • 实现set,可以考虑用map代替