Uber Go语言编码规范

Uber是世界领先的生活出行服务提供商 , 也是Go语言的早期adopter , 根据Uber工程博客的内容 , 大致可以判断出Go语言在Uber内部扮演了十分重要的角色 。Uber内部的Go语言工程实践也是硕果累累 , 有大量Go实现的内部工具被Uber开源到github上 , 诸如被Gopher圈熟知的zap、jaeger等 。2018年年末Uber将内部的Go风格规范开源到github , 经过一年的积累和更新 , 该规范已经初具规模 , 并受到广大Gopher的关注 。本文是该规范的中文版本 , 并”夹带“了部分笔者的点评 , 希望对国内Gopher有所帮助 。

注:该版本基于commit 3baa2bd翻译 , 后续不会持续更新 。

Uber Go语言编码规范

文章插图
 
一. 介绍样式(style)是支配我们代码的惯例 。术语“样式”有点用词不当 , 因为这些约定涵盖的范围不限于由gofmt替我们处理的源文件格式 。
本指南的目的是通过详细描述在Uber编写Go代码的注意事项来管理这种复杂性 。这些规则的存在是为了使代码库易于管理 , 同时仍然允许工程师更有效地使用Go语言功能 。
该指南最初由Prashant Varanasi和Simon Newton编写 , 目的是使一些同事能快速使用Go 。多年来 , 该指南已根据其他人的反馈进行了修改 。
本文档记录了我们在Uber遵循的Go代码中的惯用约定 。其中许多是Go的通用准则 , 而其他扩展准则依赖于下面外部的指南:
  • Effective Go
  • The Go common mistakes guide
所有代码都应该通过golint和go vet的检查并无错误 。我们建议您将编辑器设置为:
  • 保存时运行goimports
  • 运行golint和go vet检查源码
您可以在以下Go编辑器工具支持页面中找到更为详细的信息:https : //github.com/golang/go/wiki/IDEsAndTextEditorPlugins
二. 指导原则指向interface的指针
您几乎不需要指向接口类型的指针 。您应该将接口作为值进行传递 , 在这样的传递过程中 , 实质上传递的底层数据仍然可以是指针 。
接口实质上在底层用两个字段表示:
  • 一个指向某些特定类型信息的指针 。您可以将其视为“类型” 。
  • 数据指针 。如果存储的数据是指针 , 则直接存储 。如果存储的数据是一个值 , 则存储指向该值的指针 。
如果要接口方法修改底层数据 , 则必须用指向目标对象的指针赋值给接口类型变量(译注:感觉原指南中这里表达过于简略 , 不是很清晰 , 因此在翻译时增加了自己的一些诠释) 。
接收器(receiver)与接口
使用值接收器的方法既可以通过值调用 , 也可以通过指针调用 。
例如:
type S struct { data string}func (s S) Read() string { return s.data}func (s *S) Write(str string) { s.data = https://www.isolves.com/it/cxkf/yy/go/2019-10-14/str}sVals := map[int]S{1: {"A"}}// 你只能通过值调用ReadsVals[1].Read()// 下面无法通过编译:// sVals[1].Write("test")sPtrs := map[int]*S{1: {"A"}}// 通过指针既可以调用Read , 也可以调用Write方法sPtrs[1].Read()sPtrs[1].Write("test")同样 , 即使该方法具有值接收器 , 也可以通过指针来满足接口 。
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 Fi = s1Vali = s1Ptri = s2Ptr// 下面代码无法通过编译 。因为s2Val是一个值 , 而S2的f方法中没有使用值接收器// i = s2Val《Effective Go》中有一段关于"pointers vs values"的精彩讲解 。
译注:关于Go类型的method集合的问题 , 在我之前的文章《关于Go , 你可能不注意的7件事》中有详尽说明 。
零值Mutex是有效的
sync.Mutex和sync.RWMutex是有效的 。因此你几乎不需要一个指向mutex的指针 。
Bad:
mu := new(sync.Mutex)mu.Lock()vs.
Good:
var mu sync.Mutexmu.Lock()如果你使用结构体指针 , mutex可以非指针形式作为结构体的组成字段 , 或者更好的方式是直接嵌入到结构体中 。
如果是私有结构体类型或是要实现Mutex接口的类型 , 我们可以使用嵌入mutex的方法:
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]}


推荐阅读