
golang-code-style
热门Golang 代码风格约定——行长度与换行、变量声明、控制流清晰度、何时注释有益或有害。在编写或审查 Go 代码、询问风格或清晰度、或建立项目编码标准时使用。不适用于命名约定(→ 参见 `samber/cc-skills-golang@golang-naming` 技能)、linter 配置(→ 参见 `samber/cc-skills-golang@golang-lint` 技能)或文档注释(→ 参见 `samber/cc-skills-golang@golang-documentation` 技能)。
Golang 代码风格约定——行长度与换行、变量声明、控制流清晰度、何时注释有益或有害。在编写或审查 Go 代码、询问风格或清晰度、或建立项目编码标准时使用。不适用于命名约定(→ 参见 `samber/cc-skills-golang@golang-naming` 技能)、linter 配置(→ 参见 `samber/cc-skills-golang@golang-lint` 技能)或文档注释(→ 参见 `samber/cc-skills-golang@golang-documentation` 技能)。
社区默认。 明确取代
samber/cc-skills-golang@golang-code-style技能的公司技能优先。
Go 代码风格
需要人工判断的风格规则——linter 处理格式化,本技能处理清晰度。命名请参见 samber/cc-skills-golang@golang-naming 技能;设计模式请参见 samber/cc-skills-golang@golang-design-patterns 技能;结构体/接口设计请参见 samber/cc-skills-golang@golang-structs-interfaces 技能。
"清晰胜于巧妙。" — Go 谚语
忽略规则时,请在代码中添加注释。
行长度与换行
没有严格的行长度限制,但超过约 120 个字符的行必须换行。在语义边界处换行,而不是任意列数。参数超过 4 个的函数调用必须每个参数一行——即使提示要求单行代码:
// 好——每个参数单独一行,右括号单独一行
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
handleUsers(
w,
r,
serviceName,
cfg,
logger,
authMiddleware,
)
})
当函数签名过长时,真正的解决方法通常是减少参数(使用选项结构体),而不是更好的换行。对于多行签名,每个参数单独一行。
变量声明
对于非零值应使用 :=,对于零值初始化使用 var。形式表明意图:var 表示“从零开始”。
var count int // 零值,稍后设置
name := "default" // 非零值,:= 合适
var buf bytes.Buffer // 零值即可使用
切片与映射初始化
切片和映射必须显式初始化,绝不能为 nil。nil 映射写入时会 panic;nil 切片在 JSON 中序列化为 null(而空切片为 []),令 API 消费者意外。
users := []User{} // 始终初始化
m := map[string]int{} // 始终初始化
users := make([]User, 0, len(ids)) // 已知容量时预分配
m := make(map[string]int, len(items)) // 已知大小时预分配
不要投机性地预分配——make([]T, 0, 1000) 在常见情况只有 10 个元素时会浪费内存。
复合字面量
复合字面量必须使用字段名——位置字段在类型添加或重排字段时会出错:
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 覆盖:
// 好——默认值后用 switch 覆盖(互斥覆盖时最清晰)
level := slog.LevelInfo
switch {
case debug:
level = slog.LevelDebug
case verbose:
level = slog.LevelWarn
}
// 差——else-if 链隐藏了存在默认值
if debug {
level = slog.LevelDebug
} else if verbose {
level = slog.LevelWarn
} else {
level = slog.LevelInfo
}
复杂条件与初始化作用域
当 if 条件有 3 个以上操作数时,必须提取为命名布尔变量——一长串 || 难以阅读且隐藏业务逻辑。将昂贵的检查内联以利用短路优势。详情
// 好——命名布尔变量使意图清晰
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
}
用 Switch 替代 If-Else 链
当多次比较同一变量时,优先使用 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技能)。 - 参数顺序:
context.Context在前,然后是输入,最后是输出目标。 - 裸返回在非常短的函数(1-3 行)中有帮助,因为返回值显而易见,但当读者需要滚动查找返回值时会变得令人困惑——在较长的函数中显式命名返回值。
func FetchUser(ctx context.Context, id string) (*User, error)
func SendEmail(ctx context.Context, msg EmailMessage) error // 分组到结构体中
优先使用 range 进行迭代
应使用 range 而非基于索引的循环。使用 range n(Go 1.22+)进行简单计数。
for _, user := range users {
process(user)
}
值参数与指针参数
小类型(string、int、bool、time.Time)按值传递。在需要修改、处理大结构体(约 128+ 字节)或 nil 有意义时使用指针。详情
文件内代码组织
- 分组相关声明:类型、构造函数、方法放在一起
- 顺序:包文档、导入、常量、类型、构造函数、方法、辅助函数
- 每个文件一个主要类型(当它有重要方法时)
- 空白导入(
_ "pkg")注册副作用(init 函数)。将其限制在main和测试包中,使副作用在应用根目录可见,而不是隐藏在库代码中 - 点导入污染命名空间,使人无法判断名称来源——绝不在库代码中使用
- 积极不导出——你随时可以导出;取消导出是破坏性变更
字符串处理
简单转换使用 strconv(更快),复杂格式化使用 fmt.Sprintf。在错误消息中使用 %q 使字符串边界可见。循环中使用 strings.Builder,简单拼接使用 +。
类型转换
优先使用显式、窄转换。当具体类型可行时,使用泛型而非 any:
func Contains[T comparable](slice []T, target T) bool // 不是 []any
哲学
- "一点复制胜过一点依赖"
- 使用
slices和maps标准包;对于 filter/group-by/chunk,使用github.com/samber/lo - "反射从不清晰"——除非必要,避免使用
reflect - 不要过早抽象——在模式稳定时再提取
- 最小化公共表面——每个导出的名称都是一个承诺
并行化代码风格审查
在大型代码库中审查代码风格时,可使用最多 5 个并行子代理(通过 Agent 工具),每个针对独立的风格关注点(例如控制流、函数设计、变量声明、字符串处理、代码组织)。
使用 Linter 强制执行
许多规则由 linter 自动强制执行:gofmt、gofumpt、goimports、gocritic、revive、wsl_v5。→ 参见 samber/cc-skills-golang@golang-lint 技能。
交叉引用
- → 参见
samber/cc-skills-golang@golang-naming技能了解标识符命名约定 - → 参见
samber/cc-skills-golang@golang-structs-interfaces技能了解指针与值接收者、接口设计 - → 参见
samber/cc-skills-golang@golang-design-patterns技能了解函数选项、构建器、构造函数 - → 参见
samber/cc-skills-golang@golang-lint技能了解自动格式化强制执行 - → 参见
samber/cc-skills-golang@golang-continuous-integration技能了解在 CI 中使用这些指南进行 AI 驱动的自动代码审查





