Effective Go
代码规范
- 代码一致用
gofmt
格式化,这样就保持风格一致 - 注释文档
- 每个 package 都应该有文档,简单的包文档也可以很简单,一两行足以
- 第一行概要介绍包的作用
- 如果是一堆常量定义(例如 Error),注释可以一笔带过(liqiang 注:这个有点流于形式了?)
- package
- package 名称应该简短,就一个小写单词即可,不应该带下划线(
_
)以及 mixedCaps 格式 - 命名无需过长:Long names don’t automatically make things more readable. A helpful doc comment can often be more valuable than an extra long name.
- eg:
once.Do(setup)
better thanonce.DoOrWaitUntilDone(setup)
- eg:
- package 名称应该简短,就一个小写单词即可,不应该带下划线(
语言特性
- 条件语句
- 如果 if 的条件流程里面最后会 return,那么 else 关键词就不应该出现,他是多余的,直接在 if 段后面接上就可以了
:=
如果左边的变量在前面已经定义过了,合不合理看情况:- 如果左边的变量全都在之前定义过了,那么会报错
- 如果左边的变量部分定义过了,部分没定义,那么是合法的,并且,定义过的变量不会重新定义一遍,而是对之前的变量重新赋值
- for
- 如果你只需要 slice 的下标 或者 map 的 key,那么你只需要进行:
for key := range var
- 如果只需要值,那就需要忽略前一部分了:
for _, v := range var
- 边遍历 map 的 key,边删除 key 是合法的
- for 遍历 string 的单位是 rune,所以中文也是可以正确处理的
- 如果你只需要 slice 的下标 或者 map 的 key,那么你只需要进行:
- switch
- Go 的 switch 可以带表达式,也可以不带表达式
- 不带表达式时,从上到下遍历 case,如果 case 值为 true,执行对应的语句
- case 支持逗号分隔的多个匹配值
- defer 如果调用的是一个函数 / 方法,那么值是在调用 defer 的时候先计算好的,而不是在运行函数内容的时候再计算
- new 只是创建了一个空白的结构,并没有初始化内存,对于一些结构体来说,空白是有意义的,例如 Mutex,空白表示未加锁,所以可以直接使用;
- make 只能用于 Array/Slice/Map,它是初始化了内存的,例如初始化了数组的长度是 100
- 数组
- Go 的数组是传值的,赋值 / 传参一个 Array 不是传的指针,是会对数组的值进行拷贝
- 数组的 size 也是类型的一部分,[10] int 和 [20] int 是不同的两个类型,不能直接赋值
- 数组有个比 slice 好的地方在于他的内存是固定的,所以在多重数组的情况下,可以直接使用 array [1][2]
- map 和 slice 都是传指针的,可以修改内容
- 如果 map 取值,key 不存在,那么返回的是 value 的零值
- delete 删除不存在的 map key 是合法的
- 常量只能是 数字、多个字符(runes)、字符串和 bool 类型
- 常量是在编译时初始化的,包括函数中定义的常量,所以不能包含运行时的信息
- 变量是在运行时初始化
- interface
- 如果
interface{}
的类型断言失败,将会产生一个运行时错误 - If a type exists only to implement an interface and will never have exported methods beyond that interface, the constructor should return an interface value rather than the implementing type.
- 确保一个结构体已经实现了一个 interface 可以这个写:
var _ json.Marshaler = (*RawMessage)(nil)
- 如果
- 嵌套结构体
- When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one.
- Embedding types introduces the problem of name conflicts but the rules to resolve them are simple. First, a field or method X hides any other item X in a more deeply nested part of the type.
- if the same name appears at the same nesting level, it is usually an error;
- However, if the duplicate name is never mentioned in the program outside the type definition, it is OK.
并发
- Do not communicate by sharing memory; instead, share memory by communicating.
Thanos Coding Style Guide
开发 / Review
可靠性
CloseWithErrCapture
[root@liqiang.io]# cat runutil.go
...
// CloseWithErrCapture runs function and on error return error by argument including the given error (usually
// from caller function).
func CloseWithErrCapture(err *error, closer io.Closer, format string, a ...interface{}) {
merr := errutil.MultiError{}
merr.Add(*err)
merr.Add(errors.Wrapf(closer.Close(), format, a...))
*err = merr.Err()
}
- 不要使用 panic,如果有依赖用到了,别忘了用 recover 来处理,如果可以,放弃这个依赖吧;
- 不要在不同的块作用域使用相同的变量名字(if/for 内外)
性能
- 预先分配好 slice 和 map 的大小
- 重用 slice 的底层数组,避免重新分配
messages = messages[:0]
可以重置 slice
可读性
- 接口应该尽可能小(1-3 个方法)
- 避免 shallow function(减少不必要的抽象)
- 如果明确知道一个函数返回的错误无伤大雅,可以用
_ =
忽略 - 如果一个变量只使用一次,那么不用赋值变量了,直接在用的地方初始化就好了
- 函数 / 方法的参数,要么全在一行,要么每个参数一行
- 结构化日志应该结构化一些,不要所有信息都塞在一条日志文本中
测试
- 使用表驱动测试,这样更易于阅读
- 不要依赖实时时间做比较,请使用相对时间
- 请使用 linters
- go vet
- golangci-lint
- deadcode
- errcheck
- goconst
- goimports
- gosimple
- govet
- ineffassign
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
- 不要 print,用 logger