Files
2026-08-04 11:40:32 +08:00

5.1 KiB
Raw Permalink Blame History

name, description, license, metadata
name description license metadata
go-packages Use when creating Go packages, organizing imports, managing dependencies, or deciding how to structure Go code into packages. Also use when starting a new Go project or splitting a growing codebase into packages, even if the user doesn't explicitly ask about package organization. Does not cover naming individual identifiers (see go-naming). Apache-2.0
sources
Google Style Guide, Uber Style Guide, Go Wiki CodeReviewComments

Go 包和 Import

本技能不适用的场景:对于包内单个标识符的命名,参见 go-naming。对于单文件中函数的组织,参见 go-functions。对于强制执行 import 规则的 linter 配置,参见 go-linting。

包组织

避免 Util 包

包名应描述包提供的内容。避免使用 util、helper、common 等泛化名称——它们会模糊含义并导致 import 冲突。

// 好:有意义的包名
db := spannertest.NewDatabaseFromFile(...)
_, err := f.Seek(0, io.SeekStart)

// 不好:模糊的名称遮蔽含义
db := test.NewDatabaseFromFile(...)
_, err := f.Seek(0, common.SeekStart)

泛化名称可以作为名称的一部分(例如 stringutil),但不应成为整个包名。

Package Size

问题 操作
你能用一句话描述它的用途吗? 不能 → 按职责拆分
文件中从未共享未导出的符号? 这些文件可以是独立的包
不同的用户群体使用不同部分? 按用户边界拆分
Godoc 页面过于庞大? 拆分以提高可发现性

不要拆分的原因仅仅是文件很长、创建只有单一类型的包,或会产生循环依赖。

在决定是否拆分或合并包、组织包内文件或构建 CLI 程序时,阅读 references/PACKAGE-SIZE.md。


Import

Import 按组组织,组之间用空行分隔。标准库包始终放在第一组。使用 goimports 自动管理。

import (
    "fmt"
    "os"

    "github.com/foo/bar"
    "rsc.io/goversion/version"
)

快速规则:

规则 指导
分组 标准库优先,然后是外部包。扩展分组:标准库 → 其他 → proto → 副作用
重命名 除非冲突,否则避免重命名。重命名最本地的 import。Proto 包加 pb 后缀
空白 import(import _) 仅在 main 包或测试中使用
点 import(import .) 永不使用,除非用于循环依赖的测试文件

在组织扩展分组的 import、重命名 proto 包或决定使用空白/点 import 时,阅读 references/IMPORTS.md。


避免 init()

尽可能避免 init()。当不可避免时,它必须是:

  1. 完全确定性的
  2. 不依赖于其他 init() 的执行顺序
  3. 不依赖环境状态(环境变量、工作目录、参数)
  4. 不进行 I/O(文件系统、网络、系统调用)

可接受的使用场景:无法用单个赋值完成的复杂表达式、可插拔钩子(例如 database/sql 方言)、确定性预计算。

在需要将 init() 重构为显式函数或理解可接受的 init() 使用场景时,阅读 references/PACKAGE-SIZE.md。


Main 中的退出

仅在 main() 中调用 os.Exit 或 log.Fatal*。所有其他函数应返回 error。

原因:不明显的控制流、不可测试、defer 语句被跳过。

最佳实践:使用 run() 模式——将逻辑提取到 func run() error 中,在 main() 中调用并使用单一退出点:

func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

在实现 run() 模式、构建 CLI 子命令或选择 flag 命名约定时,阅读 references/PACKAGE-SIZE.md。


命令行 Flag

建议:仅在 package main 中定义 flag。

  • Flag 名称使用 snake_case:--output_dir 而非 --outputDir
  • 库应通过参数接收配置,而非直接读取 flag—— 这使它们可测试且可复用
  • 优先使用标准 flag 包;仅在需要 POSIX 约定 (双破折号、单字符快捷方式)时使用 pflag
// 好:Flag 在 main 中定义,作为参数传递给库
func main() {
    outputDir := flag.String("output_dir", ".", "directory for output files")
    flag.Parse()
    if err := mylib.Generate(*outputDir); err != nil {
        log.Fatal(err)
    }
}

相关技能

  • 包命名:在选择包名、避免名称重复或命名导出符号时,参见 go-naming
  • 跨包的错误处理:在使用 %w vs %v 在包边界包装错误时,参见 go-error-handling
  • Import linting:在配置 goimports local-prefixes 或强制执行 import 分组时,参见 go-linting
  • 全局状态:在用显式初始化替换 init() 或避免可变全局变量时,参见 go-defensive