Uber Go 编程风格指南
简介
本指南概述了在 Uber 编写 Go 代码的约定和最佳实践。目标是通过提供清晰的指南来管理代码复杂性,确保代码库的可维护性,同时让工程师能够有效利用 Go 的特性。
所有代码都应通过 golint
和 go vet
检查。建议在保存时运行 goimports
,并使用 golint
和 go vet
检查错误。
指南
指向接口的指针
几乎不需要使用指向接口的指针。即使底层数据是指针,接口也应作为值传递。
验证接口合规性
在适当的地方编译时验证接口合规性,以确保类型实现了所需的接口。
接收器和接口
带有值接收器的方法可以在值和指针上调用,而带有指针接收器的方法只能在指针或可寻址的值上调用。
零值 Mutex 是有效的
sync.Mutex
和 sync.RWMutex
的零值是有效的,因此很少需要指向 mutex 的指针。
在边界处复制切片和映射
切片和映射包含指向底层数据的指针,因此在复制时要小心,以避免意外的副作用。
使用 defer
清理资源
使用 defer
清理文件、锁等资源,确保即使发生错误,资源也能正确释放。
通道大小为一或无
通道的大小通常应为一或无缓冲。除非绝对必要,否则避免使用大缓冲区。
枚举从 1 开始
枚举从 1 开始,以避免零值成为有效但非预期的状态。
使用 "time"
处理时间
始终使用 time
包处理时间,以避免与时间计算相关的常见问题。
错误处理
错误类型
对于静态错误消息,使用 errors.New
;对于动态错误消息,使用 fmt.Errorf
。对于需要匹配的错误,使用自定义错误类型。
错误包装
使用 fmt.Errorf
和 %w
动词包装错误以提供上下文。
错误命名
根据错误是否导出,使用 Err
或 err
作为错误值的前缀。
只处理一次错误
只处理一次错误。避免记录错误后再返回它。
处理类型断言失败
执行类型断言时,始终使用 "comma ok" 惯用法以避免 panic。
不要 panic
在生产代码中避免使用 panic
。相反,返回错误并让调用者决定如何处理。
使用 go.uber.org/atomic
使用 go.uber.org/atomic
进行原子操作,以避免 sync/atomic
包中的常见错误。
避免可变全局变量
避免修改全局变量。使用依赖注入代替。
避免在公共结构体中嵌入类型
避免在公共结构体中嵌入类型,以防止泄露实现细节。
避免使用内置名称
避免使用 Go 的预声明标识符作为变量名,以防止遮蔽和混淆。
避免使用 init()
尽可能避免使用 init()
。如果必须使用,请确保它是确定性的,并且不依赖于外部状态。
在 main
中退出
仅在 main()
中调用 os.Exit
或 log.Fatal
。所有其他函数应返回错误。
在序列化结构体中使用字段标签
在序列化为 JSON、YAML 或其他格式的结构体中使用字段标签。
不要启动后不管的 Goroutine
确保 Goroutine 有明确的退出点,并正确清理。
性能
优先使用 strconv
而不是 fmt
将基本类型转换为字符串时,使用 strconv
而不是 fmt
,以获得更好的性能。
避免重复的字符串到字节转换
避免重复将相同的字符串转换为字节切片。转换一次并重用结果。
优先指定容器容量
尽可能指定切片和映射的容量,以避免不必要的分配。
风格
避免过长的行
避免需要水平滚动的代码行。目标是软限制为 99 个字符。
保持一致性
一致性是关键。在整个代码库中遵循相同的风格。
分组相似的声明
将相似的声明分组以提高可读性。
导入分组顺序
将导入分为标准库和第三方库。
包名
选择简短、描述性的包名,全部小写且不为复数。
函数名
使用 MixedCaps 命名函数。测试函数可以包含下划线以进行分组。
导入别名
仅在必要时使用导入别名以解决命名冲突。
函数分组和排序
按接收器分组函数,并按调用顺序排序。
减少嵌套
通过提前处理错误情况和特殊情况来减少嵌套。
不必要的 else
当变量可以在单个 if
语句中设置时,避免不必要的 else
块。
顶层变量声明
除非类型不明显,否则使用 var
进行顶层变量声明。
未导出的全局变量前缀为 _
为避免意外使用,未导出的顶层变量和常量应前缀为 _
。
结构体中的嵌入
仅在提供实际好处时才在结构体中嵌入类型。避免嵌入互斥锁。
局部变量声明
尽可能使用短变量声明 (:=
) 声明局部变量。
nil
是有效的切片
使用 nil
表示空切片,而不是显式返回空切片。
减少变量作用域
尽可能减少变量的作用域以提高可读性。
避免裸参数
避免在函数调用中使用裸参数。使用注释或命名类型以提高清晰度。
使用原始字符串字面量避免转义
使用原始字符串字面量以避免字符串中的转义字符。
初始化结构体
使用字段名初始化结构体
初始化结构体时始终使用字段名。
省略结构体中的零值字段
初始化结构体时省略零值字段。
使用 var
声明零值结构体
使用 var
声明零值结构体。
初始化结构体引用
初始化结构体引用时使用 &T{}
而不是 new(T)
。
初始化映射
使用 make
初始化空映射,使用映射字面量初始化具有固定元素的映射。
在 Printf
外部声明格式字符串
在 Printf
风格的函数外部声明格式字符串为 const
值。
命名 Printf
风格的函数
命名 Printf
风格的函数时使用 f
后缀以启用 go vet
检查。
模式
测试表
使用带有子测试的表驱动测试来避免重复代码。
函数式选项
在构造函数和公共 API 中使用函数式选项来处理可选参数。
代码检查
在整个代码库中使用一致的代码检查工具。推荐的代码检查工具包括:
errcheck
goimports
golint
govet
staticcheck
代码检查运行器
使用 golangci-lint
作为 Go 代码的代码检查运行器。它支持许多代码检查工具,并可以通过 .golangci.yml
文件进行配置。
本指南提供了在 Uber 编写 Go 代码的全面最佳实践。通过遵循这些指南,您可以确保代码的可维护性、高效性和符合 Go 的习惯用法。
版权声明: 本文为 InfoQ 作者【FunTester】的原创文章。
原文链接:【http://xie.infoq.cn/article/88638d7fbb610b8a9c8997be5】。文章转载请联系作者。
评论