这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记。
-
高质量编程简介
- 各种边界条件是否考虑完备
- 异常情况处理是否得当
- 是否保证稳定性
- 是否易读易维护
-
编码规范
-
代码格式:
gofmt:Go语言官方工具,自动格式化代码为官方统一风格
goimports:官方工具,在gofmt基础上增加了自动增删依赖包的引用,并将依赖包按字母排序分类
-
注释:
- 解释代码作用
- 解释代码实现过程
- 解释代码实现原因,需要适当补充代码的外部因素,提供需要的额外上下文
- 解释代码的出错情况or限制条件
- 公共符号or功能一定要注释(实现接口的方法除外)
-
命名规范:
-
变量:
-
简洁胜于冗长
// Bad for index := 0; index < len(s); index++ { // something } // Good for i := 0; i < len(s); i++ { // something }由于作用范围较小且意思直接明了,这里index相对于i几乎没有增加对于层序的理解
-
缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写
-
变量距离其被使用越远,则需要携带越多的上下文信息
// Bad func (c *Client) send(req *Request, t time.time) // Good func (c *Client) send(req *Request, deadline time.time)t通常表示任意时间,而deadline表述截止时间,有特定含义,如果在较远的地方使用时更清晰明了
-
-
函数:
- 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现
- 函数名尽量简短
- 函数名需要体现返回值类型信息(当返回值类型与包名一致时除外)
-
包:
- 只由小写字母组成,不包含大写字母与下划线等字符
- 简短并包含一定上下文信息
- 不要与标准库同名
- 最好不要使用常用变量名(a、b、buf等)
- 使用单数而不是复数
- 在不破坏上下文的情况下可以使用缩写
-
-
控制流程:
- 线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支
- 正常流程代码沿着屏幕向下移动
- 提升代码可维护性和可读性
- 故障问题大多出现在复杂的条件语句和循环语句中
-
错误和异常处理
-
简单错误(出现较小的错误):
- 使用errors.New创建匿名变量直接表示
- 可使用fmt.Errorf格式化错误
-
错误的Wrap和Unwrap:
- 错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
- 在fmt.Errorf中使用:%w关键字来将一个错误关联至错误链中
-
错误判定:
- 使用errors.Is判定一个错误是否为特定错误
- 使用errors.As在错误链上获取特定种类的错误
-
panic
- 不建议在业务代码中使用panic
- 调用函数不包含recover会造成程序崩溃
- 在问题可以被屏蔽或解读时,建议使用error代替panic
- 当程序启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic
-
recover
- recover只能在被defer函数中使用
- 嵌套无法生效
- 只在当前goroutine生效
- 如果需要更多上下文信息,可以recover后在log中记录当前的调用栈
-
-
-
性能优化建议
-
简介
- 性能优化的前提是满足正确可靠、简洁清晰等质量因素
- 性能优化是综合指标,有时候时间效率和空间效率可能对立
- 针对Go语言特性,介绍Go相关的性能优化建议
-
工具
- Benchmark工具
-
slice优化建议
- 尽可能在使用make()初始化切片时提供容量信息(减少申请内存次数)
- 在原有切片基础上创建切片用copy代替re-slice(避免大内存未释放)
- 原理见geektutu.com/post/hpg-sl…
-
Map优化建议
- 预分配内存(减少申请内存次数)
-
字符串处理
-
分析:
- 字符串在Go中是不可变类型,占用的内存大小是固定的
- 使用+进行字符串拼接每次都会重新分配内存
- strings.Builder,bytes.Buffer底层都是[]byte数组
-
使用+拼接字符串性能最差,strings.Builder和bytes.Buffer相近,strings.Buffer更快
-
-
空结构体
-
空结构体struct{}实例不占据任何的内存空间,可做占位符使用
// for example // 通过map实现set m := make(map[int]struct{}) // 使用bool会每个元素多占一个字节,其他数据类型更多 m := make(map[int]bool)在这个场景中只用到了map的key值,并不需要value值,就可以使用空结构体作占位符
-
-
atomic包
- 锁的实现是通过操作系统实现来实现的,属于系统调用
- atomic操作是通过硬件实现,效率比锁高
- sync.Mutex更应该用来保护一段逻辑,而不是一个变量
- 对于非数值操作,可以使用atomic.Value,能承载一个interface{}
-