这是我参与「第五届青训营」笔记创作活动的第 2 天
1. 编码规范
注释
- 包中声明的每个公共的符号:变量、常量、函数以及结构都需要添加注释
- 任何既不明显也不简短的公共功能必须予以注释
- 无论长度或复杂程度如何,对库中的任何函数都必须进行注释
- 有一个例外,不需要注释实现接口的方法
注释需要的内容
- 注释应该解释代码作用
- 注释应该解释代码如何做的
- 注释应该解释代码实现的原因
- 注释应该解释代码什么情况会出错
命名规范
变量
- 简洁胜于冗长(例如在for循环中将
index
写作i
) - 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写
- 例如使用ServeHTTP而不是 ServeHttp
- 使用XMLHTTPRequest 或者xmlHTTPRequest
- 变量距离其被使用的地方越远,则需要携带越多的上下文信息
- 全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易辨认出其含义
函数
- 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的(例如在 http 包中使用
Serve
比ServeHTTP
更好) - 函数名尽量简短
- 当名为 foo 的包某个函数返回类型 Foo 时,可以省略类型信息而不导致歧义
- 当名为 foo 的包某个函数返回类型 T 时(T 并不是 Foo),可以在函数名中加入类型信息
包
- 只由小写字母组成,不包含大写字母和下划线
- 简短并包含一定的上下文信息,例如schema、task等
- 不要与标准库同名。例如不要使用sync或者strings
以下规则尽量满足,以标准库为例
- 不使用常用变量名作为包名。例如使用 bufio 而不是 buf
- 使用单数而不是复数。例如使用 encoding 而不是 encodings
- 谨慎地使用缩写。例如使用 fmt 在不破坏上下文的情况下比 format 更加简短
控制流程
- 避免嵌套,保持正常流程清晰
- 尽量保证正常代码路径为最小缩进
错误和异常处理
简单错误
- 简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误
- 优先使用errors.New来创建匿名变量来直接表示简单错误
- 如果有格式化的需求,使用fmt.Errorf
错误的 Wrap 和 Unwrap
- 错误的Wrap 实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
- 在
fmt.Errorf
中使用: %w
关键字来将一个错误关联至错误链中,例如
err := Function(args)
if err != nil {
return fmt.Errorf("xxx: %w", err)
}
panic
panic 是比 error 严重得多的错误,一旦出现 panic 则大概率代表程序无法继续运行,因此不建议在业务代码中使用 panic。如果问题可以被屏蔽或解决,建议使用 error 代替 panic。
但是也有例外:当程序启动阶段发生不可逆转的错误时,可以在 init 或 main 函数中使用 panic。
recover
- recover 只能在被 defer 的函数中使用
- 嵌套无法生效
- 只在当前 goroutine 生效
- defer 的语句是后进先出
2. 性能调优指南
Benchmark
使用 Benchmark 进行性能基准测试
slice
初始化 slice 时尽可能提供 cap 信息
// bad
data := make([]int, 0)
// good
data := make([]int, 0, cap)
map
初始化 map 时尽可能预分配内存
// bad
data := make(map[int]int)
// good
data := make(map[int]int, size)
字符串拼接
使用 strings.Builder
// bad
s := ""
s += str
// good
// 初始化 strings.Builder 类型字符串
var builder strings.Builder
// 字符串拼接
builder.WriteString(str)
// 将 strings.Builder 类型转换为 string 类型
builder.String()
根本原因在于 Go 语言中 string 是不可变类型,占用的内存大小是固定的,因此使用 + 拼接时会重新分配内存。
而 strings.Builder
底层是 []byte
数组,在拼接时只是进行了数组扩容,因此不需要重新分配内存。
如果预先知道了最终字符串的长度,则可以使用 Grow()
预分配内存进一步提高运行速度,代码如下
var builder strings.Builder
// 预分配内存
builder.Grow(length)
builder.WriteString(str)
builder.String()
使用空结构体
使用空结构体 struct{}
可以节省内存,因为 struct{}
不占用任何空间,可作为占位符使用。
m := make(map[int]struct{})
m[0] = struct{}{}
如果不使用 map 的值,那么此时 map 可以看做 set 数据结构。
使用 atomic 包
使用 atomic 包以以下代码为例
type atomicCounter struct {
i int32
}
func AtomicAddOne(c *atomicCounter) {
atomic.AddInt32(&c.i, 1)
}
3. 性能优化分析工具
使用性能分析工具 pprof 比较重要的分析方面有
- CPU
- 堆内存-Heap
- 协程-Goroutine