代码风格
字数
1617 字
阅读时间
7 分钟
官方提供的标准参考意见:
- Effective Go
- Go Wiki: Go Code Review Comments
- Go Wiki: Home
- The Go Blog
- Style guides for Google > Go Style Best Practices
1. 命名风格
1.1 包名(Package)
- 格式:简短、全小写、无下划线,多词连写,合理缩写(如
tabwriter而非tab_writer或TabWriter)。 - 避免阴影变量:包名不应与常用局部变量名冲突。
- 导入重命名:第三方或生成包若含下划线,导入时需重命名(如
import foopb "path/to/foo_go_proto")。 - 避免无意义名称:如
util、common,缺乏功能指向,易积累无关内容,导致依赖膨胀和导入冲突。
1.2 常量(Constant)
- 格式:使用
MixedCaps,导出常量首字母大写,不导出则小写。
1.3 变量(Variable)
- 作用域相关:小作用域可用单字母(如循环
i),大作用域需描述性名称(如userCount)。 - 避免类型后缀:
userCount优于numUsers或usersInt。 - 简洁性:利用上下文省略冗余词(如方法
UserCount()内的变量可用count而非userCount)。 - 避免名称重复:使用包的时候通过包名前缀区分内容,包内成员无需重复包名(
http.Server而不是http.HTTPServer。)
1.4 接受者名字(Receiver)
- 要求:简短(1-2 字母)、类型缩写、一致性。
- 示例:
原名称 推荐名称 func (tray Tray)func (t Tray)func (info *ResearchInfo)func (ri *ResearchInfo)func (this *ReportWriter)func (w *ReportWriter)
1.5 首字母缩写(Initialisms)
- 一致性:命名中的首字母缩写需要保留一致大小写,如
XMLAPI而非XMLapi或XmlApi。 - 特例:包含一个小写字母,则保留原样,如
DDoS而非DDOS。
1.6 获取函数(Getters)
- 避免前缀:不使用
Get/get前缀,直接用名词(如Counts()优于GetCounts())。 - 增加暗示:远程调用或耗时操作可用
Fetch、Compute等代表执行是耗时间的,可能阻塞或失败。
2. import
2.1 分组
- 标准库与第三方/项目包分开,空行分隔。go
import ( "fmt" "os" "github.com/dsnet/compress/flate" "myproj/foo/proto" )
2.2 空白导入
- 仅在
main包或测试中导入仅用于副作用的包(import _ "pkg")。
2.3 import 的点
- 仅在因循环依赖无法属于被测试包的测试中使用
import . "foo",其他场景禁止(影响可读性)。go// 改为 foo_test 而不是 package foo, pacakge foo_test import ( // 通过这种方式也可以直接使用 foo 包的导出变量、函数。 . "foo" )
3. 注释
3.1 导出对象
- 所有导出的类型、函数必须有注释,以对象名开头,完整句子。go
// Request represents a request to run a command. type Request struct { ... } // Encode writes the JSON encoding of req to w. func Encode(w io.Writer, req *Request) { ... }
3.2 结构体字段
- 注释可紧跟字段,描述用途(如
BaseDir string // 存储莎士比亚作品的基础目录)。
3.3 包注释
紧邻
package声明,无空行,单个包注释覆盖整个包。 main 包的注释以命令行或二进制文件名开头。go// Package foo ... package foogo// Command xxx ... package main
4. 控制结构 & 函数 & 方法
4.1 裸返回(Naked Returns)
- 小规模函数:允许简短函数使用裸返回(无参数的
return)。gofunc split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return // 裸返回,命名结果参数 } - 中大规模函数:显式返回值,避免命名结果参数导致的冗余和可读性问题。
4.2 非必要不传指针
- 避免指针:避免在函数中传递指针参数,除非需要修改传入参数。
4.3 接受者的类型
引用类型的特殊处理:
若接收者是map、func或chan,无需使用指针(本身已是引用类型)。若接收者是slice且方法不涉及重新切片或重新分配,也无需使用指针。小且不变的类型:
对于小的、不可变的结构体(如time.Time)、数组或基本类型(int、string),值接收者是合理选择(可能减少堆内存分配,降低垃圾回收压力)。需修改接收者时:
若方法需要修改接收者,必须使用指针接收者(值接收者是原变量的拷贝,修改不会影响原变量)。含同步字段的结构体:
若结构体接收者包含如sync.Mutex这类同步字段,必须使用指针接收者,以避免因拷贝导致同步状态混乱。大对象的性能考量:
对于大的结构体或数组,指针接收者更高效。元素可变性暗示:
若接收者(结构体、数组、切片)的元素包含指针,优先使用指针接收者,表明里面的内容可以被修改。保持一致性: 避免混合使用接收者类型,所有方法应保持一致(要么全部使用指针接收者,要么全部使用值接收者)。
拿不准时选指针:
当不确定如何选择时,优先使用指针接收者。
4.4 JSON序列化
- struct 字段需首字母大写并使用 json tag。go
type User struct { FirstName string `json:"first_name"` // 字段首字母大写,json tag 下划线或小驼峰(统一即可) Age int `json:"age"` }
4.5 接口设计(Interfaces)
- 定义位置:接口应在消费方包中定义,实现方返回具体类型(如
consumer.Thinger接口在消费方包中声明)。 - 避免提前定义:仅在实际使用时定义接口
- 测试友好:通过具体类型实现接口,便于消费方模拟(Mock)而不依赖实现方接口。
