Effective Go 精要
翻译整理自 Effective Go 与 Go Code Review Comments。
本章总结官方推荐的 Go 编程习惯与最佳实践。
1. 格式化
统一使用 gofmt / go fmt 格式化代码。不要自定义风格争议,让工具决定。
go fmt ./...更强大的工具 gofumpt、goimports(补全 import、整理顺序):
go install golang.org/x/tools/cmd/goimports@latest2. 注释
注释应该是完整句子,以被注释的名字开头:
func Add(a, b int) int { return a + b }文档以 // 开头,pkg 文档放在 doc.go 文件中。
3. 命名
- 驼峰命名:
httpClient、HttpServer - 首字母大小写决定可见性
- 缩写词统一大小写:
URL、API、ID - 接收者:1-2 字母短名(如
r、c),保持一致 - 接口:通常
-er后缀(Reader、Writer、Closer)
4. 控制结构
- 没有
while、do-while,只有for if可带短语句switch默认 break,不需要写- 用
break Loop跳出多层
5. 错误处理
不要忽略错误
// ❌
file, _ := os.Open(path)
// ✅
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()哨兵错误
if errors.Is(err, io.EOF) {
// ...
}自定义错误
var ErrNotFound = errors.New("not found")
if errors.Is(err, ErrNotFound) {
// handle
}包装错误
return fmt.Errorf("query failed: %w", err)用 %w 包装,外层可用 errors.Is / errors.As 解开。
6. defer 配对
资源释放必配 defer,且紧跟在错误检查后:
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()7. 包设计
- 一个包一个职责
- 包名简短、小写、单数
- 公开 API 最小化
- 接受接口、返回结构体
- 包应无状态、可重入
8. 并发
- 优先 channel
- 启动的 goroutine 必须有退出路径,避免泄漏
- 使用
context.Context传递超时与取消 - 共享状态用
sync.Mutex/RWMutex - 不要在
main启动 goroutine 后直接返回
9. 内存与性能
- 不要过早优化
- 避免不必要的分配:
bytes.Buffer、strings.Builder、sync.Pool make切片时尽量预估容量,减少扩容拷贝append返回必须接收- struct 用
*还是值:根据大小、是否修改
var b strings.Builder
for i := 0; i < 1000; i++ {
b.WriteString("x")
}
b.String()10. 测试与可维护性
- 写表驱动测试
- benchmark 验证性能改动
- 用
golangci-lint做静态检查 - 公开 API 写 Example
11. go vet 与 linter
go vet ./...
go install honnef.co/go/tools/cmd/staticcheck@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
golangci-lint run12. 项目布局建议
参考 golang-standards/project-layout:
.
├── cmd/ # 可执行入口
├── internal/ # 仅本仓库可用的私有代码
├── pkg/ # 可被外部引用的库
├── api/ # API schema
├── configs/ # 配置
├── scripts/ # 脚本
└── go.mod
internal/是 Go 强约束:只能被internal/...父级以上的模块导入。
13. 常见陷阱
- for 循环闭包:循环变量在 Go 1.22 前会被复用,闭包需传入参数
- map 并发:并发读写 panic,用
sync.Map或锁 - nil interface:
interface(nil)不是nil,类型与值同时为 nil 才相等 - defer 参数立即求值
- 向已关闭 channel 发送会 panic
- 空切片 vs nil 切片:序列化、反射行为不同
小结
简洁、清晰、一致,是 Go 的灵魂。
Go 不追求语言特性的炫技,而追求工程化、可读性、可维护性。读完本章,你已经具备写出地道 Go 代码的全部要素。