mcpField
G
golang-code-style

golang-code-style

github.com/samber开发3.7k44.6k 次安装5 天前更新
代码仓库

关于 golang-code-style

golang-code-style 是一项 Go 代码审查与编写技能,侧重清晰度和可维护性。它补充处理格式检查器难以判断的风格问题,并可将大型审查拆分给专注于不同方面的子 Agent。

主要功能

  1. 01可读 Go 代码指导
  2. 02声明与控制流规则
  3. 03函数和文件组织建议
  4. 04类型与字符串处理建议
  5. 05并行代码风格审查

适用场景

  1. 01检查 Go 代码的清晰度
  2. 02在代码库中应用一致的风格
  3. 03将大型风格审查分配给子 Agent

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/samber/cc-skills-golang --skill golang-code-style

安装后在 Claude Code 中输入/golang-code-style确认已加载。

附带文件evals/evals.jsonreferences/details.md

详细介绍

编排模式: 在大型代码库中审查代码风格时,应启动“并行化代码风格审查”一节中描述的子 Agent,让每个子 Agent 各自负责一项独立的风格问题,并合并其发现。在 Claude Code 中,使用 ultracode 明确启用多 Agent 编排。

社区默认规则。 明确取代 samber/cc-skills-golang@golang-code-style skill 的公司 skill 优先。

Go 代码风格

需要人工判断的风格规则——代码检查工具负责格式化,本 skill 负责清晰度。命名请参阅 samber/cc-skills-golang@golang-naming skill;设计模式请参阅 samber/cc-skills-golang@golang-design-patterns skill;结构体和接口设计请参阅 samber/cc-skills-golang@golang-structs-interfaces skill。

“清晰胜于巧妙。” — Go 格言

忽略某条规则时,请在代码中添加注释。

行长与换行

没有严格的行长限制,但超过约 120 个字符的行必须换行。应在语义边界处换行,而不是按任意列数换行。包含 4 个或更多参数的函数调用必须每行写一个参数——即使提示词要求代码写在单行中:

// Good — each argument on its own line, closing paren separate
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
    handleUsers(
        w,
        r,
        serviceName,
        cfg,
        logger,
        authMiddleware,
    )
})

函数签名过长时,真正的解决办法通常是减少参数(使用选项结构体),而不是优化换行方式。对于多行签名,每个参数单独占一行。

变量声明

对于非零值,建议使用 :=;对于零值初始化,使用 var。这种形式能表明意图:var 表示“从零值开始”。

var count int              // zero value, set later
name := "default"          // non-zero, := is appropriate
var buf bytes.Buffer       // zero value is ready to use
切片与映射初始化

切片和映射必须显式初始化,不能为 nil。向 nil 映射写入会引发 panic;nil 切片在 JSON 中会序列化为 null(而空切片会序列化为 []),这会让 API 使用者感到意外。

users := []User{}                       // always initialized
m := map[string]int{}                   // always initialized
users := make([]User, 0, len(ids))      // preallocate when capacity is known
m := make(map[string]int, len(items))   // preallocate when size is known

不要臆测性地预分配内存——常见情况只有 10 个元素时,make([]T, 0, 1000) 会浪费内存。

复合字面量

复合字面量必须使用字段名——类型添加或调整字段顺序时,位置字段会出问题:

srv := &http.Server{
    Addr:         ":8080",
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 10 * time.Second,
}

控制流

减少嵌套

必须优先处理错误和边界情况(提前返回)。让正常路径保持最少的缩进:

func process(data []byte) (*Result, error) {
    if len(data) == 0 {
        return nil, errors.New("empty data")
    }

    parsed, err := parse(data)
    if err != nil {
        return nil, fmt.Errorf("parsing: %w", err)
    }

    return transform(parsed), nil
}
删除不必要的 else

当 if 语句体以 return/break/continue 结束时,必须删除 else。简单赋值时,使用先设默认值、再覆盖的方式——先赋默认值,再通过独立条件或 switch 覆盖:

// Good — default-then-override with switch (cleanest for mutually exclusive overrides)
level := slog.LevelInfo
switch {
case debug:
    level = slog.LevelDebug
case verbose:
    level = slog.LevelWarn
}

// Bad — else-if chain hides that there's a default
if debug {
    level = slog.LevelDebug
} else if verbose {
    level = slog.LevelWarn
} else {
    level = slog.LevelInfo
}
复杂条件与初始化作用域

当 if 条件包含 3 个或更多操作数时,必须提取为带名称的布尔变量——一长串 || 难以阅读,也会掩盖业务逻辑。为利用短路求值的优势,开销较大的检查应保留在行内。详情

// Good — named booleans make intent clear
isAdmin := user.Role == RoleAdmin
isOwner := resource.OwnerID == user.ID
isPublicVerified := resource.IsPublic && user.IsVerified
if isAdmin || isOwner || isPublicVerified || permissions.Contains(PermOverride) {
    allow()
}

变量仅用于检查时,应将其作用域限定在 if 代码块内:

if err := validate(input); err != nil {
    return err
}
比起连续的 If-Else 链,更倾向使用 Switch

多次比较同一个变量时,优先使用 switch:

switch status {
case StatusActive:
    activate()
case StatusInactive:
    deactivate()
default:
    panic(fmt.Sprintf("unexpected status: %d", status))
}

函数设计

  • 建议函数简短且职责明确——一个函数只做一件事。
  • 函数建议包含 ≤4 个参数。超出时,请使用选项结构体(参见 samber/cc-skills-golang@golang-design-patterns skill)。
  • 参数顺序:先放 context.Context,再放输入参数,最后放输出目标。
  • 在返回值一目了然的极短函数(1–3 行)中,裸返回可以让代码更简洁;但当读者需要滚动查找返回内容时,裸返回会令人困惑——较长的函数应显式命名返回值。
func FetchUser(ctx context.Context, id string) (*User, error)
func SendEmail(ctx context.Context, msg EmailMessage) error  // grouped into struct
遍历时优先使用 range

建议使用 range,而不是基于索引的循环。对于简单计数,可使用 range n(Go 1.22+)。

for _, user := range users {
    process(user)
}

按值传递与按指针传递

小型类型(string、int、bool、time.Time)按值传递。需要修改值、结构体较大(约 128 字节或更多)或 nil 有意义时,使用指针。详情

文件内的代码组织

  • 将相关声明分组:类型、构造函数和方法放在一起
  • 顺序:包文档、导入、常量、类型、构造函数、方法、辅助函数
  • 每个文件一个主要类型,前提是该类型有大量方法
  • 空白导入(_ "pkg")用于注册副作用(init 函数)。将其限制在 main 和测试包中,可以让副作用在应用程序根目录中显而易见,而不是隐藏在库代码里
  • 点导入会污染命名空间,让人无法判断名称的来源——库代码中绝不使用
  • 尽可能取消导出——以后随时可以导出;取消导出属于破坏性变更。→ 参见 samber/cc-skills-golang@golang-gopls skill,以安全地取消导出——它会原子化地重命名所有调用点;如果将方法名改为小写会破坏接口实现,则会拒绝此变更。这类破坏是 grep/sed 会悄然引入的。

字符串处理

简单转换使用 strconv(速度更快),复杂格式化使用 fmt.Sprintf。在错误消息中使用 %q,让字符串边界清晰可见。循环中使用 strings.Builder,简单拼接使用 +。

类型转换

优先使用显式且范围尽可能窄的转换。能使用具体类型时,优先使用泛型而非 any:

func Contains[T comparable](slice []T, target T) bool  // not []any

理念

  • “稍微复制一点代码,也胜过稍微增加一个依赖”
  • 使用标准库 slices 和 maps;对于过滤、分组和分块,使用 github.com/samber/lo
  • “反射永远不清晰”——除非必要,否则避免使用 reflect
  • 不要过早抽象——等模式稳定后再提取
  • 尽量缩小公共 API 的范围——每个导出的名称都是一项承诺

并行进行代码风格审查

审查大型代码库的代码风格时,最多使用 5 个并行子 Agent,每个负责一项独立的风格关注点(例如控制流、函数设计、变量声明、字符串处理、代码组织)。

使用 Linter 强制执行

许多规则可通过以下工具自动强制执行:gofmt、gofumpt、goimports、gocritic、revive、wsl_v5。→ 参见 samber/cc-skills-golang@golang-lint skill。

交叉引用

  • → 有关标识符命名约定,请参见 samber/cc-skills-golang@golang-naming skill
  • → 有关指针接收者与值接收者、接口设计,请参见 samber/cc-skills-golang@golang-structs-interfaces skill
  • → 有关函数选项、构建器和构造函数,请参见 samber/cc-skills-golang@golang-design-patterns skill
  • → 有关自动格式化强制执行,请参见 samber/cc-skills-golang@golang-lint skill
  • → 有关在 CI 中使用这些准则进行自动化 AI 驱动的代码审查,请参见 samber/cc-skills-golang@golang-continuous-integration skill
  • → 当审查发现大规模违规时,有关如何在多个调用点安全地机械式应用守卫子句转换、函数提取和选项结构体迁移,请参见 samber/cc-skills-golang@golang-refactoring skill

评论

支持 Markdown