mcpField
G
go-packages

go-packages

github.com/cxuu开发1861.2k 次安装19 周前更新
代码仓库

关于 go-packages

面向 Go 开发者的技能,涵盖包与导入组织、命令行程序结构及初始化模式。适用于规划 Go 项目结构和 API 边界。

主要功能

  1. 01包命名与职责划分建议
  2. 02导入分组和别名指导
  3. 03限制 init() 使用的建议
  4. 04main 函数与错误处理模式
  5. 05命令行标志设计指导

适用场景

  1. 01规划 Go 项目的包边界
  2. 02整理标准库与外部导入
  3. 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()。如无法避免,必须满足以下条件:

  1. 完全确定性
  2. 不依赖其他 init() 的顺序
  3. 不依赖环境状态(环境变量、工作目录、参数)
  4. 不执行 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