[TOC] > [参考uber](https://github.com/xxjwxc/uber_go_guide_cn) ## sync.Mutex 无需设置指针 Bad ``` mu := new(sync.Mutex) mu.Lock() ``` Good ``` var mu sync.Mutex mu.Lock() ``` ## 在边界处拷贝 Slices 和 Maps ### 接收 Slices 和 Maps 当 map 或 slice 作为函数参数传入时,如果您存储了对它们的引用,则用户可以对其进行修改 Bad ``` func (d *Driver) SetTrips(trips []Trip) { d.trips = trips } trips := ... d1.SetTrips(trips) // 你是要修改 d1.trips 吗? trips[0] = ... ``` Good ``` func (d *Driver) SetTrips(trips []Trip) { d.trips = make([]Trip, len(trips)) copy(d.trips, trips) } trips := ... d1.SetTrips(trips) // 这里我们修改 trips[0],但不会影响到 d1.trips trips[0] = ... ``` ### 返回 slices 或 maps 同样,请注意用户对暴露内部状态的 map 或 slice 的修改。 Bad ``` type Stats struct { mu sync.Mutex counters map[string]int } // Snapshot 返回当前状态。 func (s *Stats) Snapshot() map[string]int { s.mu.Lock() defer s.mu.Unlock() return s.counters } // snapshot 不再受互斥锁保护 // 因此对 snapshot 的任何访问都将受到数据竞争的影响 // 影响 stats.counters snapshot := stats.Snapshot() ``` Good ``` type Stats struct { mu sync.Mutex counters map[string]int } func (s *Stats) Snapshot() map[string]int { s.mu.Lock() defer s.mu.Unlock() result := make(map[string]int, len(s.counters)) for k, v := range s.counters { result[k] = v } return result } // snapshot 现在是一个拷贝 snapshot := stats.Snapshot() ``` ## 使用 defer 做清理 使用 defer 清理资源,诸如文件和锁。 Bad ``` p.Lock() if p.count < 10 { p.Unlock() return p.count } p.count++ newCount := p.count p.Unlock() return newCount // 当有多个 return 分支时,很容易遗忘 unlock ``` Good ``` p.Lock() defer p.Unlock() if p.count < 10 { return p.count } p.count++ return p.count // 更可读 ``` > Defer 的开销非常小,只有在您可以证明函数执行时间处于纳秒级的程度时,才应避免这样做。使用 defer 提升可读性是值得的,因为使用它们的成本微不足道。尤其适用于那些不仅仅是简单内存访问的较大的方法,在这些方法中其他计算的资源消耗远超过 defer ## Channel 的 size 要么是 1,要么是无缓冲的 channel 通常 size 应为 1 或是无缓冲的。默认情况下,channel 是无缓冲的,其 size 为零。任何其他尺寸都必须经过严格的审查。考虑如何确定大小,是什么阻止了 channel 在负载下被填满并阻止写入,以及发生这种情况时发生了什么。 Bad ``` type Operation int const ( Add Operation = iota Subtract Multiply ) // Add=0, Subtract=1, Multiply=2 ``` Good ``` type Operation int const ( Add Operation = iota + 1 Subtract Multiply ) // Add=1, Subtract=2, Multiply=3 ``` ## 枚举从 1 开始 Bad ``` type Operation int const ( Add Operation = iota Subtract Multiply ) // Add=0, Subtract=1, Multiply=2 ``` Good ``` type Operation int const ( Add Operation = iota + 1 Subtract Multiply ) // Add=1, Subtract=2, Multiply=3 ``` 在某些情况下,使用零值是有意义的(枚举从零开始),例如,当零值是理想的默认行为时。 ``` type LogOutput int const ( LogToStdout LogOutput = iota LogToFile LogToRemote ) // LogToStdout=0, LogToFile=1, LogToRemote=2 ``` ## 错误类型 客户端需要检测错误,并且您已使用创建了一个简单的错误 errors.New,请使用一个错误变量 Bad ``` // package foo func Open() error { return errors.New("could not open") } // package bar func use() { if err := foo.Open(); err != nil { if err.Error() == "could not open" { // handle } else { panic("unknown error") } } } ``` Good ``` // package foo var ErrCouldNotOpen = errors.New("could not open") func Open() error { return ErrCouldNotOpen } // package bar if err := foo.Open(); err != nil { if err == foo.ErrCouldNotOpen { // handle } else { panic("unknown error") } } ``` ## 处理类型断言失败 Bad ``` t := i.(string) ``` Good ``` t, ok := i.(string) if !ok { // 优雅地处理错误 } ``` ## 性能 ### 优先使用 strconv 而不是 fmt Bad ``` for i := 0; i < b.N; i++ { s := fmt.Sprint(rand.Int()) } //BenchmarkFmtSprint-4 143 ns/op 2 allocs/op ``` Good ``` for i := 0; i < b.N; i++ { s := strconv.Itoa(rand.Int()) } //BenchmarkStrconv-4 64.2 ns/op 1 allocs/op ``` ### 避免字符串到字节的转换 Bad ``` for i := 0; i < b.N; i++ { w.Write([]byte("Hello world")) } //BenchmarkBad-4 50000000 22.2 ns/op ``` Good ``` data := []byte("Hello world") for i := 0; i < b.N; i++ { w.Write(data) } //BenchmarkGood-4 500000000 3.25 ns/op ``` ### 尽量初始化时指定 Map 容量 为 make() 提供 容量(hint) 信息尝试在初始化时调整 map 大小, 这减少了在将元素添加到 map 时增长 map 和 分配(allocations) 的开销 注意,map 不能保证分配 hint 个容量(hint) ,因此,即使提供了容量(hint),添加元素任然可以进行分配 Bad ``` m := make(map[string]os.FileInfo) files, _ := ioutil.ReadDir("./files") for _, f := range files { m[f.Name()] = f } ``` Good ``` files, _ := ioutil.ReadDir("./files") m := make(map[string]os.FileInfo, len(files)) for _, f := range files { m[f.Name()] = f } ``` ## 规范 ### 一致性 #### 相似的声明放在一组 Bad ``` const a = 1 const b = 2 var a = 1 var b = 2 type Area float64 type Volume float64 ``` Good ``` const ( a = 1 b = 2 ) var ( a = 1 b = 2 ) type ( Area float64 Volume float64 ) ``` 仅将相关的声明放在一组。不要将不相关的声明放在一组。 Bad ``` type Operation int const ( Add Operation = iota + 1 Subtract Multiply ENV_VAR = "MY_ENV" ) ``` Good ``` type Operation int const ( Add Operation = iota + 1 Subtract Multiply ) const ENV_VAR = "MY_ENV" ``` ### 包名 * 全部小写。没有大写或下划线。 * 大多数使用命名导入的情况下,不需要重命名。 * 简短而简洁。请记住,在每个使用的地方都完整标识了该名称。 * 不用复数。例如`net/url`,而不是`net/urls`。 * 不要用“common”,“util”,“shared”或“lib”。这些是不好的,信息量不足的名称 #### 导入别名 如果程序包名称与导入路径的最后一个元素不匹配,则必须使用导入别名。 ``` import ( "net/http" client "example.com/client-go" trace "example.com/trace/v2" ) ``` ### 不必要的 else Bad ``` var a int if b { a = 100 } else { a = 10 } ``` Good ``` a := 10 if b { a = 100 } ``` ### 顶层变量声明 在顶层,使用标准`var`关键字。请勿指定类型,除非它与表达式的类型不同。 Bad ``` var _s string = F() func F() string { return "A" } ``` Good ``` var _s = F() // 由于 F 已经明确了返回一个字符串类型,因此我们没有必要显式指定_s 的类型 // 还是那种类型 func F() string { return "A" } ``` ### 对于未导出的顶层常量和变量,使用_作为前缀 Bad ``` // foo.go const ( defaultPort = 8080 defaultUser = "user" ) // bar.go func Bar() { defaultPort := 9090 ... fmt.Println("Default port", defaultPort) // We will not see a compile error if the first line of // Bar() is deleted. } ``` Good ``` // foo.go const ( _defaultPort = 8080 _defaultUser = "user" ) ``` ### 结构体中的嵌入 Bad ``` type Client struct { version int http.Client } ``` Good ``` type Client struct { http.Client version int } ``` ### 使用字段名初始化结构体 Bad ``` k := User{"John", "Doe", true} ``` Good ``` k := User{ FirstName: "John", LastName: "Doe", Admin: true, } ``` ### 本地变量声明 Bad ``` var s = "foo" ``` Good ``` s := "foo" ``` 在某些情况下,var 使用关键字时默认值会更清晰。例如,声明空切片 Bad ``` func f(list []int) { filtered := []int{} for _, v := range list { if v > 10 { filtered = append(filtered, v) } } } ``` Good ``` func f(list []int) { var filtered []int for _, v := range list { if v > 10 { filtered = append(filtered, v) } } } ``` ### nil 是一个有效的 slice nil 是一个有效的长度为 0 的 slice 您不应明确返回长度为零的切片。应该返回nil 来代替 Bad ``` if x == "" { return []int{} } ``` Good ``` if x == "" { return nil } ``` 要检查切片是否为空,请始终使用len(s) == 0。而非 nil Bad ``` func isEmpty(s []string) bool { return s == nil } ``` Good ``` func isEmpty(s []string) bool { return len(s) == 0 } ``` 零值切片(用var声明的切片)可立即使用,无需调用make()创建 Bad ``` nums := []int{} // or, nums := make([]int) if add1 { nums = append(nums, 1) } if add2 { nums = append(nums, 2) } ``` Good ``` var nums []int if add1 { nums = append(nums, 1) } if add2 { nums = append(nums, 2) } ``` ### 小变量作用域 如果有可能,尽量缩小变量作用范围。除非它与 减少嵌套的规则冲突。 Bad ``` err := ioutil.WriteFile(name, data, 0644) if err != nil { return err } ``` Good ``` if err := ioutil.WriteFile(name, data, 0644); err != nil { return err } ``` ### 避免参数语义不明确(Avoid Naked Parameters) Bad ``` // func printInfo(name string, isLocal, done bool) printInfo("foo", true, true) ``` Good ``` // func printInfo(name string, isLocal, done bool) printInfo("foo", true /* isLocal */, true /* done */) ``` Very Good ``` //对于上面的示例代码,还有一种更好的处理方式是将上面的 bool 类型换成自定义类型。 //将来,该参数可以支持不仅仅局限于两个状态(true/false)。 type Region int const ( UnknownRegion Region = iota Local ) type Status int const ( StatusReady = iota + 1 StatusDone // Maybe we will have a StatusInProgress in the future. ) func printInfo(name string, region Region, status Status) ``` ### 使用原始字符串字面值,避免转义 Bad ``` wantError := "unknown name:\"test\"" ``` Good ``` wantError := `unknown error:"test"` ``` ### 初始化 Struct 引用 在初始化结构引用时,请使用&T{}代替new(T),以使其与结构体初始化一致。 Bad ``` sval := T{Name: "foo"} // inconsistent sptr := new(T) sptr.Name = "bar" ``` Good ``` sval := T{Name: "foo"} sptr := &T{Name: "bar"} ``` ### 字符串 string format 使用 const 如果你为Printf-style 函数声明格式字符串,请将格式化字符串放在外面,并将其设置为const常量。 这有助于go vet对格式字符串执行静态分析 Bad ``` msg := "unexpected values %v, %v\n" fmt.Printf(msg, 1, 2) ``` Good ``` const msg = "unexpected values %v, %v\n" fmt.Printf(msg, 1, 2) ``` ## 编程模式 Bad ``` host, port, err := net.SplitHostPort("192.0.2.0:8000") require.NoError(t, err) assert.Equal(t, "192.0.2.0", host) assert.Equal(t, "8000", port) host, port, err = net.SplitHostPort("192.0.2.0:http") require.NoError(t, err) assert.Equal(t, "192.0.2.0", host) assert.Equal(t, "http", port) host, port, err = net.SplitHostPort(":8000") require.NoError(t, err) assert.Equal(t, "", host) assert.Equal(t, "8000", port) host, port, err = net.SplitHostPort("1:8") require.NoError(t, err) assert.Equal(t, "1", host) assert.Equal(t, "8", port) ``` Good ``` // func TestSplitHostPort(t *testing.T) tests := []struct{ give string wantHost string wantPort string }{ { give: "192.0.2.0:8000", wantHost: "192.0.2.0", wantPort: "8000", }, { give: "192.0.2.0:http", wantHost: "192.0.2.0", wantPort: "http", }, { give: ":8000", wantHost: "", wantPort: "8000", }, { give: "1:8", wantHost: "1", wantPort: "8", }, } for _, tt := range tests { t.Run(tt.give, func(t *testing.T) { host, port, err := net.SplitHostPort(tt.give) require.NoError(t, err) assert.Equal(t, tt.wantHost, host) assert.Equal(t, tt.wantPort, port) }) } ``` ## 功能选项 Bad ``` // package db func Connect( addr string, timeout time.Duration, caching bool, ) (*Connection, error) { // ... } // Timeout and caching must always be provided, // even if the user wants to use the default. db.Connect(addr, db.DefaultTimeout, db.DefaultCaching) db.Connect(addr, newTimeout, db.DefaultCaching) db.Connect(addr, db.DefaultTimeout, false /* caching */) db.Connect(addr, newTimeout, false /* caching */) ``` Good ``` type options struct { timeout time.Duration caching bool } // Option overrides behavior of Connect. type Option interface { apply(*options) } type optionFunc func(*options) func (f optionFunc) apply(o *options) { f(o) } func WithTimeout(t time.Duration) Option { return optionFunc(func(o *options) { o.timeout = t }) } func WithCaching(cache bool) Option { return optionFunc(func(o *options) { o.caching = cache }) } // Connect creates a connection. func Connect( addr string, opts ...Option, ) (*Connection, error) { options := options{ timeout: defaultTimeout, caching: defaultCaching, } for _, o := range opts { o.apply(&options) } // ... } // Options must be provided only if needed. db.Connect(addr) db.Connect(addr, db.WithTimeout(newTimeout)) db.Connect(addr, db.WithCaching(false)) db.Connect( addr, db.WithCaching(false), db.WithTimeout(newTimeout), ) ``` > 如果Good 的案例看不懂,请查看 [技巧&规范](../go/%E6%8A%80%E5%B7%A7.md) 的实例