Uber Go 语言编程规范

本文介绍了Uber的Go语言编程规范,涵盖介绍、编程指南、性能相关、编程风格和编程模式等方面。内容包括指向Interface的指针使用、Receiver与Interface、mutex的使用、拷贝Slice和Map的注意事项、defer的正确使用、channel的最佳实践、枚举变量的声明、Error类型处理、类型转换错误处理、避免panic、使用go.uber.org/atomic、类型转换优化以及编程风格的多项建议,旨在提高代码的维护性和开发效率。
摘要由CSDN通过智能技术生成

Uber Go 语言编程规范

相信很多人前两天都看到 Uber 在 github 上面开源的 Go 语言编程规范了,原文在这里:https://github.com/uber-go/guide/blob/master/style.md 。我们今天就来简单了解一下国外大厂都是如何来写代码的。行文仓促,错误之处,多多指正。另外如果觉得还不错,也欢迎分享给更多的人。

1. 介绍

英文原文标题是 Uber Go Style Guide,这里的 Style 是指在管理我们代码的时候可以遵从的一些约定。

这篇编程指南的初衷是更好的管理我们的代码,包括去编写什么样的代码,以及不要编写什么样的代码。我们希望通过这份编程指南,代码可以具有更好的维护性,同时能够让我们的开发同学更高效地编写 Go 语言代码。

这份编程指南最初由 Prashant VaranasiSimon Newton 编写,旨在让其他同事快速地熟悉和编写 Go 程序。经过多年发展,现在的版本经过了多番修改和改进了。这是我们在 Uber 遵从的编程范式,但是很多都是可以通用的,如下是其他可以参考的链接:

所有的提交代码都应该通过 golintgo vet 检测,建议在代码编辑器上面做如下设置:

  • 保存的时候运行 goimports
  • 使用 golintgo vet 去做错误检测。

你可以通过下面链接发现更多的 Go 编辑器的插件: https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins

2. 编程指南

2.1 指向 Interface 的指针

在我们日常使用中,基本上不会需要使用指向 interface 的指针。当我们将 interface 作为值传递的时候,底层数据就是指针。Interface 包括两方面:

  • 一个包含 type 信息的指针
  • 一个指向数据的指针

如果你想要修改底层的数据,那么你只能使用 pointer。

2.2 Receiver 和 Interface

使用值作为 receiver 的时候 method 可以通过指针调用,也可以通过值来调用。

type S struct {
   
  data string
}

func (s S) Read() string {
   
  return s.data
}

func (s *S) Write(str string) {
   
  s.data = str
}

sVals := map[int]S{
   1: {
   "A"}}

// You can only call Read using a value
sVals[1].Read()

// This will not compile:
//  sVals[1].Write("test")

sPtrs := map[int]*S{
   1: {
   "A"}}

// You can call both Read and Write using a pointer
sPtrs[1].Read()
sPtrs[1].Write("test")

相似的,pointer 也可以满足 interface 的要求,尽管 method 使用 value 作为 receiver。

type F interface {
   
  f()
}

type S1 struct{
   }

func (s S1) f() {
   }

type S2 struct{
   }

func (s *S2) f() {
   }

s1Val := S1{
   }
s1Ptr := &S1{
   }
s2Val := S2{
   }
s2Ptr := &S2{
   }

var i F
i = s1Val
i = s1Ptr
i = s2Ptr

// The following doesn't compile, since s2Val is a value, and there is no value receiver for f.
//   i = s2Val

Effective Go 关于如何使用指针和值也有一些不错的 practice: Pointers vs. Values.

2.3 mutex 默认 0 值是合法的

sync.Mutexsync.RWMutex 的 0 值也是合法的,所以我们基本不需要声明一个指针指向 mutex。

Bad

mu := new(sync.Mutex)
mu.Lock()

Good

var mu sync.Mutex
mu.Lock()

如果 struct 内部使用 mutex,在我们使用 struct 的指针类型时候,mutex 也可以是一个非指针类型的 field,或者直接嵌套在 struct 中。

Mutex 直接嵌套在 struct 中。

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]
}

将 Mutex 作为一个 struct 内部一个非指针类型 Field 使用。

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]
}
2.4 拷贝 Slice 和 Map

Slice 和 Map 都包含了对底层存储数据的指针,所以注意在修改 slice 或者 map 数据的场景下,是不是使用了引用。

slice 和 map 作为参数

当把 slice 和 map 作为参数的时候,如果我们对 slice 或者 map 的做了引用操作,那么修改会修改掉原始值。如果这种修改不是预期的,那么要先进行 copy。

Bad

func (d *Driver) SetTrips(trips []Trip) {
   
  d.trips = trips
}

trips := ...
d1.SetTrips(trips)

// Did you mean to modify 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)

// We can now modify trips[0] without affecting d1.trips.
trips[0] = ...
slice 和 map 作为返回值

当我们的函数返回 slice 或者 map 的时候,也要注意是不是直接返回了内部数据的引用到外部。

Bad

type Stats struct {
   
  sync.Mutex

  counters map[string]int
}

// Snapshot returns the current stats.
func (s *Stats) Snapshot() map[string]int {
   
  s.Lock()
  defer s.Unlock()

  return s.counters
}

// snapshot is no longer protected by the lock!
snapshot := stats.Snapshot()

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 is now a copy.
snapshot := stats.Snapshot()
2.5 使用 defer 做资源清理

建议使用 defer 去做资源清理工作,比如文件,锁等。

Bad

p.Lock()
if p.count < 10 {
   
  p.Unlock()
  return p.count
}

p.count++
newCount := p.count
p.Unlock()

return newCount

// easy to miss unlocks due to multiple returns

Good

p.Lock()
defer p.Unlock()

if p.count < 10 {
   
  return p.count
}

p.count++
return p.count

// more readable

尽管使用 defer 会导致一定的性能开销,但是大部分情况下这个开销在你的整个链路上所占的比重往往是微乎其微,除非说真的是有非常高的性能需求。另外使用 defer 带来的代码可读性的改进以及减少代码发生错误的概率都是值得的。

2.6 channel 的 size 最好是 1 或者是 unbuffered

在使用 channel 的时候,最好将 size 设置为 1 或者使用 unbuffered channel。其他 size 的 channel 往往都会引入更多的复杂度,需要更多考虑上下游的设计。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值