Go 基础编码规范与性能优化
Go 编码规范
风格
自动格式化代码工具:
- gofmt
- goimports
GoLand 内置了这些工具。
注释
注释内容:
- 代码作用
- 代码如何如何做的
- 代码实现的原因
- 代码什么情况下会出错(代码的限制条件)
公共符号始终要注释;
注释应该提供代码未表达出的上下文信息。
命名规范
降低阅读代码成本,重点考虑上下文信息,设计简介清晰的名称。
Good naming is like a good joke. If you have to explain it, it’s not funny
—— Dave Cheney
variable
缩略词使用全大写 ServeHTTP
而非 ServerHttp
。若不需要导出,则使用全小写。
变量距离被使用的地方越远,则命名中需要携带越多的上下文信息。`
1 | // Good 使用"deadline"能够在变量名中携带更多信息量 |
function
-
函数名不携带包名的上下文信息
-
函数名应该尽量简短
-
当名为 foo 的包的某个函数返回 Foo 类型时,可以在函数名省略其类型信息
1
2
3
4
5package http
// good
func Serve(...)
// bad
func ServeHTTP(...)在调用时:
http.Serve(...)
-
当 foo 包的某个函数返回类型 T 时,可以在函数名中加入类型信息
1
2
3
4
5
6package time
// bad
func Parse(s String) (Duration, error)
// good
func ParseDuration(s string) (Duration, error)在调用时:
duration := time.ParseDuration(...)
package
必须满足:
- 只由小写字母组成
- 简短、包含一定的上下文信息
- 不要于标准库同名
尽量满足:
- 不是用常用变量名作为包名
- 使用单数而非复数
- 谨慎地使用缩写
控制流程
-
避免嵌套。
-
优先处理错误情况,尽早返回循环。若 if 有
return
则省略else
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// bad
if A {
return
} else {
if B {
return
} else {
if C {
return
} else {
...
}
}
}
// good
if A {
return
}
if B {
return
}
if C {
return
}
...
错误与异常处理
-
简单的错误(仅出现一次,且其他地方不需要捕获)
1
errors.New("Simple Error")
-
复杂错误
1
2
3
4
5
6
7// err 错误由其他代码抛出,在此处进行关联 补充错误上下文信息
fmt.Errorf("reading srcfiles list: %w", err)
// 判断错误链是否有指定错误
errors.Is(err1, err2)
// 取出特定错误
errors.As(err3, &err) -
panic 程序遇到无法解决的异常
- 不建议在业务代码中使用
- 调用函数不包含
recorver
会造成程序崩溃 - 若错误可以屏蔽或解决,建议使用
error
- 当程序启动阶段发生不可逆转的错误时,可以在
init
或main
中使用 panic
-
recover
只能在
defer
中使用1
2
3
4
5defer func() {
if e:= recover() && e != nil {
err = fmt.Errorf("error: %v \n %s", err, debug.Stack())
}
}
Go 性能优化
Benchmark
1 | go test -bench=. -benchmem |
技巧
slice
-
尽可能在初始化时提供容量信息
1
make([]int, capSize)
-
使用
copy
代替re-slice
以解决大内存未释放的问题由于类似于
origin[1:100]
这样的用法虽然创建了一个新的 Slice,但是还是复用着原 Slice 底层的数组,而如果原 Slice 不再使用的话,底层数组也会跟随新的 Slice 一直存在。使用copy
函数可以避免此问题。1
copy(result, origin[1:100])
map
预分配内存
1 | make(map[string]int, capSize) |
减少扩容带来的 Rehash 和内存分配的耗时
拼接字符串
字符串在 Go 中时不可变类型,占用内存大小的固定的,每次 + 都会重新分配内存。strings.Builder 等底层时 []byte,并根据内存扩容策略,无需每次拼接都重新分配切片。
- 使用 + 性能最差
- strings.Builder, bytes.Buffer 相近,bytes.Buffer 转化为字符串时重新申请空间,而 strings.Builder 使用原有的空间
- strings.Buffer 最快
空结构体
空结构体不占据任何内存空间,可作为占位符使用。
atomic
通过硬件实现,效率比锁高
sync.Mutex
应用来保护一段逻辑而非只是一个变量
对于非数值操作,可以使用 atomic.Value
,可以承载一个interface{}
测试工具
pprof