原文地址:https://github.com/uber-go/guide/blob/master/style.md
译文出处:https://github.com/uber-go/guide
本文永久链接:
https://github.com/gocn/translator/blob/master/2019/w38ubergostyleguide.md
译者:咔叽咔叽
校对者:fivezh,cvley
目录
介绍
指南
接口的指针
接收者和接口
零值 Mutexes 是有效的
复制 Slice 和 Map
Defer 的使用
channel 的大小是 1 或者 None
枚举值从 1 开始
Error 类型
Error 包装
处理类型断言失败
避免 Panic
使用 go.uber.org/atomic
性能
strconv 优于 fmt
避免 string 到 byte 的转换
代码样式
聚合相似的声明
包的分组导入的顺序
包命名
函数命名
别名导入
函数分组和顺序
减少嵌套
不必要的 else
顶层变量的声明
在不可导出的全局变量前面加上 _
结构体的嵌入
使用字段名去初始化结构体
局部变量声明
nil 是一个有效的 slice
减少变量的作用域
避免裸参数
使用原生字符串格式来避免转义
初始化结构体
在 Printf 之外格式化字符串
Printf-style 函数的命名
设计模式
表格驱动测试
函数参数可选化
介绍
代码风格是代码的一种约定。用风格这个词可能有点不恰当,因为这些约定涉及到的远比源码文件格式工具 gofmt 所能处理的更多。
本指南的目标是通过详细描述 Uber 在编写 Go 代码时的取舍来管理代码的这种复杂性。这些规则的存在是为了保持代码库的可管理性,同时也允许工程师更高效地使用 go 语言特性。
本指南最初由 Prashant Varanasi 和 Simon Newton 为了让同事们更便捷地使用 go 语言而编写。多年来根据其他人的反馈进行了一些修改。
本文记录了 uber 在使用 go 代码中的一些习惯用法。许多都是 go 语言常见的指南,而其他的则延伸到了一些外部资料:
Effective Go
The Go common mistakes guide
所用的代码在运行 golint
和 go vet
之后不会有报错。建议将编辑器设置为:
保存时运行 goimports
运行
golint
和go vet
来检查错误
你可以在下面的链接找到 Go tools 对一些编辑器的支持:https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins
指南
接口的指针
你几乎不需要指向接口的指针,应该把接口当作值传递,它的底层数据仍然可以当成一个指针。
一个接口是两个字段:
指向特定类型信息的指针。你可以认为这是 "type."。
如果存储的数据是指针,则直接存储。如果数据存储的是值,则存储指向此值的指针。
如果你希望接口方法修改底层数据,则必须使用指针。
接收者和接口
具有值接收者的方法可以被指针和值调用。
例如,
type S struct {
data string
}
func (s S) Read() string {
return s.data
}
func (s *S) Write(str string) {
s.data = str
}
sVals := map[int]S{1: {"A"}}
// 使用值只能调用 Read 方法
sVals[1].Read()
// 会编译失败
// sVals[0].Write("test")
sPtrs := map[int]*S{1: {"A"}}
// 使用指针可以调用 Read 和 Write 方法
sPtrs[1].Read()
sPtrs[1].Write("test")
类似的,即使方法是一个值接收者,但接口仍可以被指针类型所满足。
type F interface {
f()
}
type S1 struct{}
func (s S1) f() {}
type S2 struct{}
func (s *S2) f() {}
s1Val := S1{}
s1Ptr := &S1{}
s2Val := S2{}
s2Ptr := &S2{}
var i F
i = s1Val
i = s1Ptr
i = s2Ptr
// 以下不能被编译,因为 s2Val 是一个值,并且 f 没有值接收者
// i = s2Val
Effective Go 对 Pointers vs. Values 分析的不错.
零值 Mutexes 是有效的
零值的 sync.Mutex
和 sync.RWMutex
是有效的,所以你几乎不需要指向 mutex 的指针。
var mu sync.Mutex
mu.Lock()
defer mu.Unlock()
如果你使用一个指针指向的结构体,mutex 可以作为一个非指针字段,或者,最好是直接嵌入这个结构体。
复制 Slice 和 Map
slice 和 map 包含指向底层数据的指针,因此复制的时候需要当心。
接收 Slice 和 Map 作为入参
需要留意的是,如果你保存了作为参数接收的 map 或 slice 的引用,可以通过引用修改它。
返回 Slice 和 Map
类似的,当心 map 或者 slice 暴露的内部状态是可以被修改的。
Defer 的使用
使用 defer 去关闭文件句柄和释放锁等类似的这些资源。
defer 的开销非常小,只有在你觉得你的函数执行需要在纳秒级别的情况下才需要考虑避免使用。使用 defer 换取的可读性是值得的。这尤其适用于具有比简单内存访问更复杂的大型方法,这时其他的计算比 defer 更重要。
channel 的大小是 1 或者 None
channel 的大小通常应该是 1 或者是无缓冲的。默认情况下,channel 是无缓冲的且大小为 0。任何其他的大小都必须经过仔细检查。应该考虑如何确定缓冲的大小,哪些因素可以防止 channel 在负载时填满和阻塞写入,以及当这种情况发生时会造成什么样的影响。
枚举值从 1 开始
在 Go 中引入枚举的标准方法是声明一个自定义类型和一个带 iota
的 const
组。由于变量的默认值为 0,因此通常应该以非零值开始枚举。
在某些情况下,使用零值是有意义的,例如零值是想要的默认值。
type LogOutp