在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称:xxjwxc/uber_go_guide_cn开源软件地址:https://github.com/xxjwxc/uber_go_guide_cn开源编程语言:开源软件介绍:uber-go/guide 的中文翻译EnglishUber Go 语言编码规范Uber 是一家美国硅谷的科技公司,也是 Go 语言的早期 adopter。其开源了很多 golang 项目,诸如被 Gopher 圈熟知的 zap、jaeger 等。2018 年年末 Uber 将内部的 Go 风格规范 开源到 GitHub,经过一年的积累和更新,该规范已经初具规模,并受到广大 Gopher 的关注。本文是该规范的中文版本。本版本会根据原版实时更新。 版本
目录
介绍样式 (style) 是支配我们代码的惯例。术语 本指南的目的是通过详细描述在 Uber 编写 Go 代码的注意事项来管理这种复杂性。这些规则的存在是为了使代码库易于管理,同时仍然允许工程师更有效地使用 Go 语言功能。 该指南最初由 Prashant Varanasi 和 Simon Newton 编写,目的是使一些同事能快速使用 Go。多年来,该指南已根据其他人的反馈进行了修改。 本文档记录了我们在 Uber 遵循的 Go 代码中的惯用约定。其中许多是 Go 的通用准则,而其他扩展准则依赖于下面外部的指南: 所有代码都应该通过
您可以在以下 Go 编辑器工具支持页面中找到更为详细的信息: https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins 指导原则指向 interface 的指针您几乎不需要指向接口类型的指针。您应该将接口作为值进行传递,在这样的传递过程中,实质上传递的底层数据仍然可以是指针。 接口实质上在底层用两个字段表示:
如果希望接口方法修改基础数据,则必须使用指针传递 (将对象指针赋值给接口变量)。 type F interface {
f()
}
type S1 struct{}
func (s S1) f() {}
type S2 struct{}
func (s *S2) f() {}
// f1.f() 无法修改底层数据
// f2.f() 可以修改底层数据,给接口变量 f2 赋值时使用的是对象指针
var f1 F = S1{}
var f2 F = &S2{} Interface 合理性验证在编译时验证接口的符合性。这包括:
补充:上面 3 条是编译器对接口的检查机制, 大体意思是错误使用接口会在编译期报错。 所以可以利用这个机制让部分问题在编译期暴露。
如果 赋值的右边应该是断言类型的零值。
对于指针类型(如 type LogHandler struct {
h http.Handler
log *zap.Logger
}
var _ http.Handler = LogHandler{}
func (h LogHandler) ServeHTTP(
w http.ResponseWriter,
r *http.Request,
) {
// ...
} 接收器 (receiver) 与接口使用值接收器的方法既可以通过值调用,也可以通过指针调用。 带指针接收器的方法只能通过指针或 addressable values 调用。 例如, 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[1].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 是一个值,而 S2 的 f 方法中没有使用值接收器
// i = s2Val Effective Go 中有一段关于 pointers vs. values 的精彩讲解。 补充:
具体的匹配分两种:
为啥 i = s2Val 会报错,因为值方法集和接口不匹配。 零值 Mutex 是有效的零值
如果你使用结构体指针,mutex 应该作为结构体的非指针字段。即使该结构体不被导出,也不要直接把 mutex 嵌入到结构体中。
在边界处拷贝 Slices 和 Mapsslices 和 maps 包含了指向底层数据的指针,因此在需要复制它们时要特别注意。 接收 Slices 和 Maps请记住,当 map 或 slice 作为函数参数传入时,如果您存储了对它们的引用,则用户可以对其进行修改。
返回 slices 或 maps同样,请注意用户对暴露内部状态的 map 或 slice 的修改。
使用 defer 释放资源使用 defer 释放资源,诸如文件和锁。
Defer 的开销非常小,只有在您可以证明函数执行时间处于纳秒级的程度时,才应避免这样做。使用 defer 提升可读性是值得的,因为使用它们的成本微不足道。尤其适用于那些不仅仅是简单内存访问的较大的方法,在这些方法中其他计算的资源消耗远超过 Channel 的 size 要么是 1,要么是无缓冲的channel 通常 size 应为 1 或是无缓冲的。默认情况下,channel 是无缓冲的,其 size 为零。任何其他尺寸都必须经过严格的审查。我们需要考虑如何确定大小,考虑是什么阻止了 channel 在高负载下和阻塞写时的写入,以及当这种情况发生时系统逻辑有哪些变化。(翻译解释:按照原文意思是需要界定通道边界,竞态条件,以及逻辑上下文梳理)
枚举从 1 开始在 Go 中引入枚举的标准方法是声明一个自定义类型和一个使用了 iota 的 const 组。由于变量的默认值为 0,因此通常应以非零值开头枚举。
在某些情况下,使用零值是有意义的(枚举从零开始),例如,当零值是理想的默认行为时。 type LogOutput int
const (
LogToStdout LogOutput = iota
LogToFile
LogToRemote
)
// LogToStdout=0, LogToFile=1, LogToRemote=2 使用 time 处理时间时间处理很复杂。关于时间的错误假设通常包括以下几点。
例如,1 表示在一个时间点上加上 24 小时并不总是产生一个新的日历日。 因此,在处理时间时始终使用
使用 |
Bad | Good |
---|---|
func isActive(now, start, stop int) bool {
return start <= now && now < stop
} |
func isActive(now, start, stop time.Time) bool {
return (start.Before(now) || start.Equal(now)) && now.Before(stop)
} |
time.Duration
表达时间段在处理时间段时使用 time.Duration
.
Bad | Good |
---|---|
func poll(delay int) {
for {
// ...
time.Sleep(time.Duration(delay) * time.Millisecond)
}
}
poll(10) // 是几秒钟还是几毫秒? |
func poll(delay time.Duration) {
for {
// ...
time.Sleep(delay)
}
}
poll(10*time.Second) |
回到第一个例子,在一个时间瞬间加上 24 小时,我们用于添加时间的方法取决于意图。如果我们想要下一个日历日 (当前天的下一天) 的同一个时间点,我们应该使用 Time.AddDate
。但是,如果我们想保证某一时刻比前一时刻晚 24 小时,我们应该使用 Time.Add
。
newDay := t.AddDate(0 /* years */, 0 /* months */, 1 /* days */)
maybeNewDay := t.Add(24 * time.Hour)
time.Time
和 time.Duration
尽可能在与外部系统的交互中使用 time.Duration
和 time.Time
例如 :
flag
通过 time.ParseDuration
支持 time.Duration
encoding/json
通过其 UnmarshalJSON
method 方法支持将 time.Time
编码为 RFC 3339 字符串database/sql
支持将 DATETIME
或 TIMESTAMP
列转换为 time.Time
,如果底层驱动程序支持则返回gopkg.in/yaml.v2
支持将 time.Time
作为 RFC 3339 字符串,并通过 time.ParseDuration
支持 time.Duration
。当不能在这些交互中使用 time.Duration
时,请使用 int
或 float64
,并在字段名称中包含单位。
例如,由于 encoding/json
不支持 time.Duration
,因此该单位包含在字段的名称中。
Bad | Good |
---|---|
// {"interval": 2}
type Config struct {
Interval int `json:"interval"`
} |
// {"intervalMilli |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论