Go 编码规范:格式、命名、接口与评审
本轮只做理论正文与源码静态复核;下列命令、示例和测试均未执行。已有输出按代码推演或历史记录理解,不作为本轮运行证据。
先遵循项目已有约定,再用以下规则评审可读性、接口与验证方式。Go 工具和官方惯例提供共同基础,团队自己的版本号、行宽、测试框架和覆盖率要求应在贡献指南里明确。
格式、命名与声明
- 使用
gofmt,导入排序由工具处理;行宽按可读性和团队约定判断,不手动与工具争夺格式。 - 包名简短、小写并有明确含义,调用处无需重复包名,例如
user.Service通常比user.UserService更清楚。局部变量名可短,跨越较长作用域的名字应表达用途。 - 导出标识符首字母大写;
userID、HTTPClient等首字母缩写保持惯用大小写。常量通常使用MaxRetries、maxRetries,全大写下划线不是 Go 的通用惯例。 - 布尔名称表达状态即可,例如
ready、enabled,不必全部加 Is/Has。接口按能力命名,如Reader;不要预先把实现的全部方法搬进大接口。 - 参数按逻辑关系排列;需要 context 时通常把
ctx context.Context放在第一位,error放在结果最后。相邻同类型参数可以合写,多个同型字符串容易误传时应重新考虑 API。
导入与包依赖
标准库与其他导入通常用空行分组,是否将组织内部包再分组由项目决定。导入路径是模块路径加包的相对路径,不需要真的能被浏览器直接打开。Modules 工程不使用 ./subpackage 导入。
避免点导入,必要的别名应解释冲突或用途。空白导入 _ 用于显式依赖初始化副作用,例如某些 SQL 驱动;普通未使用依赖应删除,不以空白导入掩盖。工具安装和模块依赖更新见模块管理。
注释与文档
导出的类型、函数、常量组和包应说明用途与约束,文档注释通常以标识符名称开头,并写成完整句子。注释解释业务限制、生命周期、并发安全和错误语义,不逐行翻译实现。较长包说明可以放在 doc.go。
// Reader reads records from a stream. It is not safe for concurrent use.
type Reader interface {
ReadRecord() (Record, error)
}此处 Record 代表业务类型,是接口设计片段。可执行示例放入 example_test.go,用 Output 注释让 go test 验证。TODO/FIXME 要有具体未完成事项,必要时关联问题编号。
控制流、错误与数据
优先提前返回,保持正常路径清楚;失败要添加足够的操作上下文并保留错误链。不要忽略读取、解析和数据库操作的错误。资源成功取得后再 defer 释放;为阻塞操作提供超时与取消,并确认 goroutine 何时返回。
结构体用带字段名的字面量初始化。接收者要匹配修改语义、方法集和复制限制,包含锁的对象尤其不能在使用后复制。共享 map 或切片的所有权应明确。函数摆放顺序以相关定义易查为准,不强制全部项目遵守 CRUD 顺序或插入大段字符画。
边界复制与公开接口
接收并长期保存外部 slice/map,或返回内部集合时,明确调用方是否可修改。需要独立所有权就复制;含指针的元素仍是浅复制。可用 slices.Clone、maps.Clone 或显式遍历,但不能将复制当作并发同步手段。不要无意通过嵌入类型暴露其全部方法,API 只公开使用方需要的能力。
接口通常按值传递,底层对象可以是指针。编译期可用局部片段 var _ Finisher = (*Task)(nil) 检查实现关系,前提是 Finisher 与 Task 已按接口章节定义;不需要给接口变量再加一层指针来修改对象。
团队约定与语言保证
旧 Uber 译本中“枚举从1开始”“channel容量只能是0或1”“包级私有变量加下划线”等是团队选择,不是语言限制。根据零值含义设计枚举,根据背压与所有权设计通道容量。当前标准库已有类型化 atomic 值,不能把额外原子库说成唯一类型安全方案。
避免可变全局状态,优先构造函数传入依赖;init 用于确有必要的初始化,不把复杂网络连接藏进去。nil slice 与空 slice 在 JSON 等边界可能不同,应按契约决定。不要遮蔽 nil、len 等预声明名称;原始字符串可简化多行文本,但仍需理解其中的换行。
功能选项适合逐步扩展可选配置的 API,选项要验证范围、冲突和默认值。普通参数已经清楚时不增加模式。性能方面,容量提示、strconv 或减少转换只在实际负载和基准支持下采用,不承诺绝对更快。
业务函数返回 error,main 统一决定退出码;os.Exit 和 log.Fatal 会跳过尚未执行的 defer,不宜在库函数中随意退出进程。
项目结构与版本
Go 没有适用于所有应用的强制目录模板。小程序可先保持单一 main 包;有多个可执行入口时可用 cmd/<name>,需要限制模块外部导入的实现放 internal。按实际职责拆包,避免为了目录齐全建立空的 models/service/repository。
示意结构:
go.mod
cmd/server/main.go
internal/user/
internal/storage/配置、模板、静态资源按应用需要加入;运行日志和临时数据通常不是源码结构的一部分。版本表达由发布流程决定,不要求所有 main 包包含 APP_VER,也不要求库实现 Version 函数。
测试与验收
测试文件使用 _test.go,函数遵循 TestXxx、BenchmarkXxx 等约定。优先覆盖正常输入、失败分支、边界值和外部契约;覆盖率阈值由风险与项目要求决定。修复缺陷先复现,再确认测试能够捕获回归。测试课程提供可运行的表格测试、示例和基准。
常规验证:go fmt ./...、go vet ./...、go test ./...;涉及共享数据时在支持的平台增加 go test -race ./...。
进一步阅读
团队的具体约定可以写在项目贡献指南中。参考 Go Code Review Comments、Effective Go 与 组织模块文档。许可证应以项目 LICENSE 和实际采用的条款为准,不从源文件头部模板推断授权。
评审练习与参考答案
题目:函数返回内部[]*Record副本,是否已经隔离所有修改?提示:区分切片元素副本和指针对象。
参考答案
没有。复制切片只隔离元素槽位,指向的Record仍共享;应按所有权合同选择深复制、不可变对象或受控方法。格式化与静态检查不能代替这一语义判断;Record是文档中的业务占位类型,不是缺失的标准库定义。
继续阅读
- 开发实践
- 知识专题
- 开发实践