[TOC] ## 规范 ### 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 在负载下被填满并阻止写入,以及发生这种情况时发生了什么。 ### 枚举从 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 { // 优雅地处理错误 } ``` ### 包名 * 全部小写。没有大写或下划线。 * 大多数使用命名导入的情况下,不需要重命名。 * 简短而简洁。请记住,在每个使用的地方都完整标识了该名称。 * 不用复数。例如`net/url`,而不是`net/urls`。 * 不要用“common”,“util”,“shared”或“lib”。这些是不好的,信息量不足的名称 #### 导入别名 如果程序包名称与导入路径的最后一个元素不匹配,则必须使用导入别名。 ``` import ( "net/http" client "example.com/client-go" trace "example.com/trace/v2" ) ``` ### 顶层变量声明 在顶层,使用标准`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) ``` ### 基于指针对象的方法 如果某个 struct 方法操作不需要修改 strcut的值,则不需要指针,如 has ,需要修改值 如 add 则需要添加指针方法 ### 使用 fmt.Errorf 代替 errors.New()