Uber 是一家美国硅谷的科技公司,也是 Go 语言的早期 adopter。其开源了很多 golang 项目,诸如被 Gopher 圈熟知的 zap、jaeger 等。2018 年年末 Uber 将内部的 Go 风格规范 开源到 GitHub,经过一年的积累和更新,该规范已经初具规模,并受到广大 Gopher 的关注,本文是对规范的整理

来源:https://github.com/xxjwxc/uber_go_guide_cn

1. 优先使用 strconv 而不是 fmt

下面代码是将int转成字符串,使用两种方式进行性能对比。

1.1 反面示例

for i := 0; i < b.N; i++ {
s := fmt.Sprint(rand.Int())
}
// 压测 每次操作 耗时:143纳秒,进行2次内存操作
// BenchmarkFmtSprint-4 143 ns/op 2 allocs/op

1.2 推荐用法

for i := 0; i < b.N; i++ {
s := strconv.Itoa(rand.Int())
}
// 压测 每次操作 耗时:64.2纳秒,进行1次内存操作
// BenchmarkStrconv-4 64.2 ns/op 1 allocs/op

2. 避免字符串到字节的转换

不要反复从固定字符串创建字节 slice。相反,应该只执行一次转换并保存结果到变量。

2.1 反面示例

for i := 0; i < b.N; i++ {
w.Write([]byte("Hello world"))
}
// 压测 每次操作 耗时:22.2 纳秒
// BenchmarkBad-4 50000000 22.2 ns/op

2.2 推荐用法

data := []byte("Hello world")
for i := 0; i < b.N; i++ {
w.Write(data)
}
// 压测 每次操作 耗时:3.25 纳秒
// BenchmarkGood-4 500000000 3.25 ns/op

3. 指定map、slice容量

指定容量,能提升代码性能,特别是在追加切片时

3.1 反面示例

for n := 0; n < b.N; n++ {
data := make([]int, 0)
for k := 0; k < size; k++{
data = append(data, k)
}
}
// BenchmarkBad-4 100000000 2.48s

3.2 推荐用法

for n := 0; n < b.N; n++ {
data := make([]int, 0, size)
for k := 0; k < size; k++{
data = append(data, k)
}
}
// BenchmarkGood-4 100000000 0.21s

4.并发锁(Mutex)

4.1 零值是有效的

零值 sync.Mutexsync.RWMutex 是有效的。所以声明时不需要使用关键字new

Bad Good
mu := new(sync.Mutex)
mu.Lock()
var mu sync.Mutex
mu.Lock()

4.2 在结构体中使用

如果使用结构体指针,mutex 应作为结构体的非指针字段。即使该结构体不被导出,也不能直接把 mutex 嵌入到结构体中。

1. 反面示例

type SMap struct {
sync.Mutex // 这里是直接嵌入
data map[string]string
}

func NewSMap() *SMap {
return &SMap{
data: make(map[string]string),
}
}

func (m *SMap) Get(k string) string {
m.Lock()
defer m.Unlock()

return m.data[k]
}

嵌入写法,会使LockUnlock 成为 SMap 导出方法,在外面可直接使用。

2. 推荐用法

type SMap struct {
mu sync.Mutex
data map[string]string
}

func NewSMap() *SMap {
return &SMap{
data: make(map[string]string),
}
}

func (m *SMap) Get(k string) string {
m.mu.Lock()
defer m.mu.Unlock()

return m.data[k]
}

mutex 及其方法是 SMap 的实现细节,对其调用者不可见。

5. 使用sync/atomic执行原子操作

使用 sync/atomic 包的原子操作对原始类型 (int32, int64等)进行操作,因为很容易忘记使用原子操作来读取或修改变量。

go.uber.org/atomic 通过隐藏基础类型为这些操作增加了类型安全性。此外,它包括一个方便的atomic.Bool类型。

5.1 反面示例

type foo struct {
running int32 // atomic
}

func (f* foo) start() {
if atomic.SwapInt32(&f.running, 1) == 1 {
// already running…
return
}
// start the Foo
}

func (f *foo) isRunning() bool {
return f.running == 1 // race!
}

5.2 推荐示例

type foo struct {
running atomic.Bool
}

func (f *foo) start() {
if f.running.Swap(true) {
// already running…
return
}
// start the Foo
}

func (f *foo) isRunning() bool {
return f.running.Load()
}