【重磅】Uber Go 语言代码风格指南

  • 原文地址:https://github.com/uber-go/guide/blob/master/style.md

  • 译文出处:https://github.com/uber-go/guide

  • 本文永久链接:

  • https://github.com/gocn/translator/blob/master/2019/w38ubergostyleguide.md

  • 译者:咔叽咔叽

  • 校对者:fivezh,cvley

目录

  • 介绍

  • 指南

  • 接口的指针

  • 接收者和接口

  • 零值 Mutexes 是有效的

  • 复制 Slice 和 Map

  • Defer 的使用

  • channel 的大小是 1 或者 None

  • 枚举值从 1 开始

  • Error 类型

  • Error 包装

  • 处理类型断言失败

  • 避免 Panic

  • 使用 go.uber.org/atomic

  • 性能

  • strconv 优于 fmt

  • 避免 string 到 byte 的转换

  • 代码样式

  • 聚合相似的声明

  • 包的分组导入的顺序

  • 包命名

  • 函数命名

  • 别名导入

  • 函数分组和顺序

  • 减少嵌套

  • 不必要的 else

  • 顶层变量的声明

  • 在不可导出的全局变量前面加上 _

  • 结构体的嵌入

  • 使用字段名去初始化结构体

  • 局部变量声明

  • nil 是一个有效的 slice

  • 减少变量的作用域

  • 避免裸参数

  • 使用原生字符串格式来避免转义

  • 初始化结构体

  • 在 Printf 之外格式化字符串

  • Printf-style 函数的命名

  • 设计模式

  • 表格驱动测试

  • 函数参数可选化

介绍

代码风格是代码的一种约定。用风格这个词可能有点不恰当,因为这些约定涉及到的远比源码文件格式工具 gofmt 所能处理的更多。

本指南的目标是通过详细描述 Uber 在编写 Go 代码时的取舍来管理代码的这种复杂性。这些规则的存在是为了保持代码库的可管理性,同时也允许工程师更高效地使用 go 语言特性。

本指南最初由 Prashant Varanasi  Simon Newton 为了让同事们更便捷地使用 go 语言而编写。多年来根据其他人的反馈进行了一些修改。

本文记录了 uber 在使用 go 代码中的一些习惯用法。许多都是 go 语言常见的指南,而其他的则延伸到了一些外部资料:

  1. Effective Go

  2. The Go common mistakes guide

所用的代码在运行 golint  go vet 之后不会有报错。建议将编辑器设置为:

  • 保存时运行 goimports

  • 运行 golint  go vet 来检查错误

你可以在下面的链接找到 Go tools 对一些编辑器的支持:https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins

指南

接口的指针

你几乎不需要指向接口的指针,应该把接口当作值传递,它的底层数据仍然可以当成一个指针。

一个接口是两个字段:

  1. 指向特定类型信息的指针。你可以认为这是 "type."。

  2. 如果存储的数据是指针,则直接存储。如果数据存储的是值,则存储指向此值的指针。

如果你希望接口方法修改底层数据,则必须使用指针。

接收者和接口

具有值接收者的方法可以被指针和值调用。

例如,

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"}}	
	
// 使用值只能调用 Read 方法	
sVals[1].Read()	
	
// 会编译失败	
//  sVals[0].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 F	
i = s1Val	
i = s1Ptr	
i = s2Ptr	
	
// 以下不能被编译,因为 s2Val 是一个值,并且 f 没有值接收者	
//   i = s2Val

Effective Go 对 Pointers vs. Values 分析的不错.

零值 Mutexes 是有效的

零值的 sync.Mutex  sync.RWMutex 是有效的,所以你几乎不需要指向 mutex 的指针。

640?wx_fmt=png

var mu sync.Mutex	
	
mu.Lock()	
defer mu.Unlock()

如果你使用一个指针指向的结构体,mutex 可以作为一个非指针字段,或者,最好是直接嵌入这个结构体。

640?wx_fmt=png

复制 Slice 和 Map

slice 和 map 包含指向底层数据的指针,因此复制的时候需要当心。

接收 Slice 和 Map 作为入参

需要留意的是,如果你保存了作为参数接收的 map 或 slice 的引用,可以通过引用修改它。

640?wx_fmt=png

返回 Slice 和 Map

类似的,当心 map 或者 slice 暴露的内部状态是可以被修改的。

640?wx_fmt=png

Defer 的使用

使用 defer 去关闭文件句柄和释放锁等类似的这些资源。

640?wx_fmt=png

defer 的开销非常小,只有在你觉得你的函数执行需要在纳秒级别的情况下才需要考虑避免使用。使用 defer 换取的可读性是值得的。这尤其适用于具有比简单内存访问更复杂的大型方法,这时其他的计算比 defer 更重要。

channel 的大小是 1 或者 None

channel 的大小通常应该是 1 或者是无缓冲的。默认情况下,channel 是无缓冲的且大小为 0。任何其他的大小都必须经过仔细检查。应该考虑如何确定缓冲的大小,哪些因素可以防止 channel 在负载时填满和阻塞写入,以及当这种情况发生时会造成什么样的影响。

640?wx_fmt=png

枚举值从 1 开始

在 Go 中引入枚举的标准方法是声明一个自定义类型和一个带 iota  const 组。由于变量的默认值为 0,因此通常应该以非零值开始枚举。

640?wx_fmt=png

在某些情况下,使用零值是有意义的,例如零值是想要的默认值。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值