关于 go-coding-standards
go-coding-standards 是面向 Go 开发者和编码 Agent 的开发技能,涵盖惯用约定与代码质量检查,并提供文件组织、接收器和标签的详细参考。
主要功能
- 01导入分组与命名规则
- 02变量和结构体初始化指引
- 03嵌套、排序与行长约定
- 04接收器、时间和结构体标签指引
- 05goimports、go vet 和 staticcheck 检查
适用场景
- 01按 Go 惯用约定审查代码
- 02整理声明与结构体字段
- 03完成代码前执行质量检查
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ eduardo-sl/ go-agent-skills --skill go-coding-standards安装后在 Claude Code 中输入/go-coding-standards确认已加载。
附带文件references/file-organization.mdreferences/receivers-and-tags.md
详细介绍
Go 编码规范
基于 Effective Go、Go Code Review Comments 和经过生产验证的惯用写法制定的 Go 惯例。
所有代码 MUST 通过 goimports、go vet 和 staticcheck(或 golangci-lint run)检查,且不得出现错误。
社区默认规范。若项目技能或 Agent 配置规则明确取代
go-coding-standards,则以该规则为准。
按需加载的详细参考资料:
references/file-organization.md— 声明分组、文件顺序、 拆分过长的函数签名。references/receivers-and-tags.md— 接收者一致性和结构体标签 示例。
仅当以下摘要不够充分时,才阅读参考文件。
1. 导入排序
按以下顺序分组导入,并以空行分隔:
import (
// 1. Standard library
"context"
"fmt"
"log/slog"
"net/http"
// 2. External packages
"github.com/gorilla/mux"
// 3. Internal/project packages
"github.com/myorg/myproject/internal/service"
)
绝不使用点导入。仅在解决命名冲突时使用别名。
2. 命名
命名由 go-naming 技能负责:包、重复包名前缀、首字母缩写、
getter、接收者、错误和测试名称。此处保留两条规则,因为它们
与声明有关:不得遮蔽预声明标识符(len、
new、error、min、max);当代码库采用此
惯例时,未导出的包级变量可以带 _ 前缀(_defaultTimeout)。
3. 变量声明
顶层
顶层声明使用 var。若类型与表达式相符,则不要指定类型:
// ✅ Good
var _defaultPort = 8080
var _logger = slog.Default()
// ❌ Bad — redundant type
var _defaultPort int = 8080
局部
- 局部变量优先使用
:=。 - 仅当有意且有意义地进行零值初始化时才使用
var。
// ✅ Good — zero value is meaningful
var buf bytes.Buffer
// ✅ Good — short declaration
name := getUserName()
4. 结构体初始化
始终使用字段名;除非零值字段具有特定含义,否则省略它们——位置 字面量在添加或重新排列字段时可能悄无声息地出错:
// ✅ Good
user := User{Name: "Alice", Email: "[email protected]"}
// ❌ Bad — positional
user := User{"Alice", "[email protected]", 0}
5. 减少嵌套
优先处理错误和特殊情况,并尽早返回,让正常路径保持在最左侧。
若 if 已经返回,则去掉对应的 else;优先先赋默认值,再按需覆盖:
// ✅ Good — guard clauses; no else
for _, v := range data {
if !v.IsValid() {
continue
}
if err := v.Process(); err != nil {
return fmt.Errorf("process %s: %w", v.ID, err)
}
}
a := 10 // default, then override
if condition {
a = 20
}
6. 分组和排序
- 将相关的
const和var声明分组在同一个括号块中。 - 文件顺序:常量和变量、构造函数、导出方法 (按重要性排序,而非按字母顺序)、未导出方法、辅助函数。
- 方法紧接在其所属类型之后。
7. 行长度
软性上限为 99 个字符。将过长的函数签名拆分为每行一个参数,
并将右括号单独放在一行。示例:
references/file-organization.md。
8. Defer 的使用
使用 defer 进行清理,以便在获取资源时清晰表达意图:
mu.Lock()
defer mu.Unlock()
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("open %s: %w", path, err)
}
defer f.Close() // read-only; check Close errors on files you write
9. 枚举
将零值保留给明确的 Unknown 哨兵值,避免未设置的值被误认为
真实状态:
type Status int
const (
StatusUnknown Status = iota
StatusActive
StatusInactive
)
10. 正确使用 time 包
- 时长使用
time.Duration,不要使用原始整数。 - 时间点使用
time.Time。使用time.Since(start),而非time.Now().Sub(start)。 - 当格式无法承载
time.Duration(JSON、YAML)时,在字段名中注明单位 —TimeoutSeconds int— 并在边界处进行转换。
// ✅ Good
func poll(interval time.Duration) { ... }
poll(10 * time.Second)
// ❌ Bad
func poll(intervalSecs int) { ... }
poll(10)
11. 接收者
每种类型选择一种接收者,并在所有方法中保持一致——混用接收者
会导致方法集随值的持有方式而变化。若有任何方法会修改值、结构体
包含 sync.Mutex 或其他不可复制字段、结构体较大,或者其他方法
已经需要指针,则使用指针接收者。小型不可变类型使用值接收者。
接收者命名为类型名后接一到两个字母(s *Server),且所有方法
保持一致,绝不使用 this 或 self。示例:references/receivers-and-tags.md。
12. 结构体标签
编译器不会检查标签,因此拼写错误不会报错。
- 对跨越序列化边界的类型,所有字段都要添加标签,即使字段名已经匹配也一样。
- 对任何绝不能离开进程的内容,使用
json:"-"—— 未导出的字段会被静默跳过,看起来像是疏忽。 omitempty无法区分“缺失”和“零值”;需要区分时请使用指针或包装类型。omitempty永远不会省略结构体;对于time.Time等字段,请使用omitzero(Go 1.24+),它会遵循 Go 的零值和IsZero()。- 运行
go vet—— 其structtag检查可发现格式错误的标签。
13. 函数形式
- 条件包含三个或更多操作数时,为各部分命名(
isOwner、isAdmin),让if语句读起来就是业务规则。 ctx context.Context放在首位,其后是输入参数,再后是选项。- 参数超过约四个,或多个同类型参数相邻时,应使用选项结构体或函数式选项 —— 两者均由 go-design-patterns skill 负责。
- 仅在函数短到可以看清所有返回值时使用裸返回。
验证清单
在认为代码已完成前:
goimports运行无误go vet ./...通过golangci-lint run通过(如果已配置)- 没有遮蔽内置标识符
- 所有导入都已正确分组和排序
- 结构体初始化使用字段名
- 没有不必要的嵌套或
else代码块 - 同一类型的所有方法接收者类型保持一致
- 每个序列化结构体字段都带有显式标签