Uber Go语言编码规范( 二 )

对于导出类型 , 请使用私有锁:
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]}在边界处拷贝Slices和Maps
slices和maps包含了指向底层数据的指针 , 因此在需要复制它们时要特别注意 。
接收Slices和Maps请记住 , 当map或slice作为函数参数传入时 , 如果您存储了对它们的引用 , 则用户可以对其进行修改 。
Bad
func (d *Driver) SetTrips(trips []Trip) { d.trips = trips}trips := ...d1.SetTrips(trips)// 你是要修改d1.trips吗?trips[0] = ...vs.
Good
func (d *Driver) SetTrips(trips []Trip) { d.trips = make([]Trip, len(trips)) copy(d.trips, trips)}trips := ...d1.SetTrips(trips)// 这里我们修改trips[0] , 但不会影响到d1.tripstrips[0] = ...返回slices或maps
同样 , 请注意用户对暴露内部状态的map或slice的修改 。
Bad
type Stats struct { sync.Mutex counters map[string]int}// Snapshot返回当前状态func (s *Stats) Snapshot() map[string]int { s.Lock() defer s.Unlock() return s.counters}// snapshot不再受到锁的保护snapshot := stats.Snapshot()vs.
Good
type Stats struct { sync.Mutex counters map[string]int}func (s *Stats) Snapshot() map[string]int { s.Lock() defer s.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.countp.Unlock()return newCount// 当有多个return分支时 , 很容易遗忘unlockvs.
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
// 应该足以满足任何人c := make(chan int, 64)vs.
Good
// 大小:1c := make(chan int, 1) // 或// 无缓冲channel , 大小为0c := make(chan int)枚举从1开始在Go中引入枚举的标准方法是声明一个自定义类型和一个使用了iota的const组 。由于变量的默认值为0 , 因此通常应以非零值开头枚举 。
Bad
type Operation intconst ( Add Operation = iota Subtract Multiply)// Add=0, Subtract=1, Multiply=2vs.
Good
type Operation intconst ( Add Operation = iota + 1 Subtract Multiply)// Add=1, Subtract=2, Multiply=3在某些情况下 , 使用零值是有意义的(枚举从零开始) , 例如 , 当零值是理想的默认行为时 。
type LogOutput intconst ( LogToStdout LogOutput = iota LogToFile LogToRemote)// LogToStdout=0, LogToFile=1, LogToRemote=2错误类型Go中有多种声明错误(Error)的选项:

  • errors.New 对于简单静态字符串的错误
  • fmt.Errorf 用于格式化的错误字符串
  • 实现Error()方法的自定义类型
  • 使用 "pkg/errors".Wrap的wrApped error
返回错误时 , 请考虑以下因素以确定最佳选择:
  • 这是一个不需要额外信息的简单错误吗?如果是这样 , errors.New 就足够了 。
  • 客户需要检测并处理此错误吗?如果是这样 , 则应使用自定义类型并实现该Error()方法 。
  • 您是否正在传播下游函数返回的错误?如果是这样 , 请查看本文后面有关错误包装(Error Wrap)部分的内容
  • 否则 , fmt.Errorf就可以 。
如果客户端需要检测错误 , 并且您已使用创建了一个简单的错误errors.New , 请使用一个错误变量(sentinel error ) 。
Bad
// package foofunc Open() error { return errors.New("could not open")}// package barfunc use() { if err := foo.Open(); err != nil { if err.Error() == "could not open" { // handle } else { panic("unknown error") } }}


推荐阅读