关于 go-packages
面向 Go 开发者的技能,涵盖包与导入组织、命令行程序结构及初始化模式。适用于规划 Go 项目结构和 API 边界。
主要功能
- 01包命名与职责划分建议
- 02导入分组和别名指导
- 03限制 init() 使用的建议
- 04main 函数与错误处理模式
- 05命令行标志设计指导
适用场景
- 01规划 Go 项目的包边界
- 02整理标准库与外部导入
- 03设计可测试的命令行程序
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ cxuu/ golang-skills --skill go-packages安装后在 Claude Code 中输入/go-packages确认已加载。
附带文件references/IMPORTS.mdreferences/PACKAGE-SIZE.md
详细介绍
Go 包和导入
资源路由
references/IMPORTS.md- 在对导入进行分组、使用空白导入、点导入或导入别名时阅读。references/PACKAGE-SIZE.md- 在拆分包、避免使用init、组织main或设计 CLI 标志/子命令时阅读。
此技能不适用的情况:如需了解如何命名包内的单个标识符,请参阅 go-naming。如需了解如何组织单个文件中的函数,请参阅 go-functions。如需了解如何配置用于强制执行导入规则的代码检查工具,请参阅 go-linting。
包组织
避免使用 Util 包
包名应说明该包提供的内容。避免使用 util、helper、common 等通用名称——它们会模糊含义并导致导入冲突。
// Good: Meaningful package names
db := spannertest.NewDatabaseFromFile(...)
_, err := f.Seek(0, io.SeekStart)
// Bad: Vague names obscure meaning
db := test.NewDatabaseFromFile(...)
_, err := f.Seek(0, common.SeekStart)
通用名称可以用作名称的一部分(例如 stringutil),但不应作为完整的包名。
包大小
| 问题 | 操作 |
|---|---|
| 能否用一句话描述其用途? | 不能 → 按职责拆分 |
| 文件之间是否从不共享未导出符号? | 这些文件可以拆分成不同的包 |
| 不同用户群体是否使用不同部分? | 按用户边界拆分 |
| Godoc 页面是否过于庞大? | 拆分以提升可发现性 |
不要仅仅因为文件很长就拆分,也不要为了创建仅包含单一类型的包而拆分,或在拆分会造成循环依赖时进行拆分。
导入
导入项按组组织,各组之间以空行分隔。标准库包始终放在最前面。使用 goimports 自动管理导入。
import (
"fmt"
"os"
"github.com/foo/bar"
"rsc.io/goversion/version"
)
快速规则:
| 规则 | 指引 |
|---|---|
| 分组 | 标准库在前,其次是外部依赖。扩展顺序:标准库 → 其他 → 原型 → 副作用 |
| 重命名 | 除非发生冲突,否则避免重命名。重命名作用域最局部的导入。Proto 包使用 pb 后缀 |
空白导入(import _) | 仅用于 main 包或测试 |
点导入(import .) | 切勿使用,循环依赖测试文件除外 |
避免使用 init()
尽可能避免使用 init()。如无法避免,必须满足以下条件:
- 完全确定性
- 不依赖其他
init()的顺序 - 不依赖环境状态(环境变量、工作目录、参数)
- 不执行 I/O(文件系统、网络、系统调用)
可接受的用途:无法通过单次赋值完成的复杂表达式、可插拔钩子(例如 database/sql 方言)、确定性预计算。
在 Main 中退出
只有在 main() 中才调用 os.Exit 或 log.Fatal*。所有其他函数都应返回错误。
原因:控制流不明显、无法测试、defer 语句会被跳过。
最佳实践:采用 run() 模式——将逻辑提取到 func run() error 中,并在 main() 中通过单一退出点调用:
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
命令行标志
建议:仅在
package main中定义标志。
- 标志名使用
snake_case:使用--output_dir,而不是--outputDir - 库应将配置作为参数接收,而不是直接读取标志——这样更便于测试和复用
- 优先使用标准
flag包;仅在需要 POSIX 约定(双连字符、单字符快捷方式)时使用pflag
// Good: Flag in main, passed as parameter to library
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与%v包装错误时,请参阅 go-error-handling - 导入检查:在配置 goimports 本地前缀或强制执行导入分组时,请参阅 go-linting
- 全局状态:在用显式初始化替代
init()或避免可变全局变量时,请参阅 go-defensive
评论
支持 Markdown