高质量编程
编程原则:
- 简单性:以简单逻辑写代码(让团队队友阅读容易)
- 可读性:编写可维护代码的第一步是确保代码可读,代码是写给人看的
- 生产力:团队整体工作效率非常重要
代码编码规范:
公共符号必须要注释;
- 包含声明的每个公共符号,常量,变量,函数,结构等都要添加注释;
- 任何既不明显也不简短的公共功能必须注释;
- 无论长度或者复杂程度,库中的任何函数都要注释 例外:不需要注释实现接口的方法
代码格式规范:推荐用gofmt可以自动格式化代码
- gofmt是go语言官方提供的工具,能自动格式化go语言代码为官方统一风格
- goimports:go语言官方提供的工具,实际等于gofmt加上依赖包管理,自动增删依赖包的引用,将依赖包按字母排序并分类
注释规范:
- 注释解释代码作用(注释公共符号)
- 注释应该解释代码是如何做的(注释代码实现过程,如for循环)
- 注释应该解释代码实现的原因(解释代码的外部因素,对脱离上下文难以解释的代码进行注释,相当于提供额外上下文)
- 注释应该解释代码什么情况下会出错(解释代码的限制条件,什么情况下出错)
- 公共符号始终都要注释(尽管LimitedReader.Read本身没有注释,但他紧跟LimitedReader结构的声明,明确它的作用)
命名规范(variable):
- 简介胜于冗长
- 缩略词全大写,但是当其位于变量开头且不需要导出,全部使用小写
- 变量距离被使用的地方越远,则需要携带越多的上下文信息,全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易便认出其含义
- 变量名如果可以准确表现含义,就没必要让变量名变简洁
- 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
- 函数名尽量简短
- 当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义(返回类型和包名一致)
- 当名为foo的包某个函数返回类型T时(T不是Foo),可以在函数名中加入类型信息
- 包名(package):只由小写字母组成,不包含大写字母和下划线等字符--简短并包含一定的上下文信息(例如schema)--不要与标准库同名,例如strings
以下规则尽量满足: 不使用常用变量作为包名,使用单数而不是复数(encoding而不是encodings),谨慎使用缩写,例如fmt在不破坏上下文情况下用format更简洁
控制流程:
- 避免嵌套,保持正常流程清晰(如果两个分支都包含return,可以去除多余的else)
- 尽量保持正常代码路径为最小缩进(优先处理错误/特殊情况,今早返回或继续循环减少嵌套)
- 简单错误:指仅仅出现一次的错误,且在其他地方不需要捕获该错误;优先使用errors.New来创建匿名变量直接表示简单错误;如有格式化的需求,使用fmt.Errorf
错误和异常处理:
- 错误的Wrap和Unwrap:错误的wrap实际上提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链;在fmt.Errorf中使用%w关键字来将一个错误关联到错误链中
- 错误判定:判定是否特定错误,使用该方法可以判定错误链上的所有错误是否含有特定的错误;
- 在错误链上获取特定种类的错误,使用errors.As
- panic:不建议在业务代码中使用;调用函数不包含recover会造成程序崩溃;若问题可以被屏蔽或解决,建议用error代替panic;
- 程序启动阶段发生不可逆转的错误,可以在Init或者main函数中使用panic(尽早暴露错误)
- recover:只能在被defer的函数中使用;嵌套无法生效;只在当前goroutine生效;defer的语句先进后出;如果需要更多的上下文信息,可以recover后在log中记录当前调用栈
关于性能优化
下面提供几个性能优化方面的建议:
go语言提供了支持基准性能测试的benchmark工具:
go tese-bench=. -benchman
性能表现要用实际数据衡量
slice预分配内存:
- 使用make()初始化切片时提供内存容量信息;
- 切片本质时一个数组片段的描述,包括数组指针,片段长度,片段容量(不改变内存分配情况下的最大长度);
- 切片并不复制切片指向的元素;
- 创建一个新的切片会复用原来切片的底层数组;
- 底层数组会在超过容量时扩容,会影响时间,所以尽量在初始化时设置好值;
slice另一个陷阱:大内存未释放:在已有切片基础上创建切片,不会创建新的底层数组,那原来的底层数组在内存中的引用得不到释放。
解决办法: 用copy代替re-slice
map
map预分配内存:map可以自动扩容
字符串处理
- 使用string.Builder拼接字符串,性能最差,拼接时不重新分配内存
- 用string.Buffer更快,他会重新申请空间
使用空结构体节省内存:
- 空结构体struct{}实例不占用任何内存,可以做占位符,节省资源
- 实现set,可以考虑用map代替