ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[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() ``` ### 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 { // 优雅地处理错误 } ``` ### 对于未导出的顶层常量和变量,使用_作为前缀 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 ``` 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 } ``` ### 使用原始字符串字面值,避免转义 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"} ``` ### 使用 fmt.Errorf 代替 errors.New()