Community default. A company skill that explicitly supersedes samber/cc-skills-golang@golang-code-style skill takes precedence.
Go Code Style
Style rules that require human judgment — linters handle formatting, this skill handles clarity. For naming see samber/cc-skills-golang@golang-naming skill; for design patterns see samber/cc-skills-golang@golang-design-patterns skill; for struct/interface design see samber/cc-skills-golang@golang-structs-interfaces skill.
"Clear is better than clever." — Go Proverbs
When ignoring a rule, add a comment to the code.
Line Length & Breaking
No rigid line limit, but lines beyond ~120 characters MUST be broken. Break at semantic boundaries, not arbitrary column counts. Function calls with 4+ arguments MUST use one argument per line — even when the prompt asks for single-line code:
CODEBLOCK0
When a function signature is too long, the real fix is often fewer parameters (use an options struct) rather than better line wrapping. For multi-line signatures, put each parameter on its own line.
Variable Declarations
SHOULD use := for non-zero values, var for zero-value initialization. The form signals intent: var means "this starts at zero."
CODEBLOCK1
Slice & Map Initialization
Slices and maps MUST be initialized explicitly, never nil. Nil maps panic on write; nil slices serialize to null in JSON (vs [] for empty slices), surprising API consumers.
CODEBLOCK2
Do not preallocate speculatively — make([]T, 0, 1000) wastes memory when the common case is 10 items.
Composite Literals
Composite literals MUST use field names — positional fields break when the type adds or reorders fields:
CODEBLOCK3
Control Flow
Reduce Nesting
Errors and edge cases MUST be handled first (early return). Keep the happy path at minimal indentation:
CODEBLOCK4
Eliminate Unnecessary else
When the if body ends with return/break/continue, the else MUST be dropped. Use default-then-override for simple assignments — assign a default, then override with independent conditions or a switch:
CODEBLOCK5
Complex Conditions & Init Scope
When an if condition has 3+ operands, MUST extract into named booleans — a wall of || is unreadable and hides business logic. Keep expensive checks inline for short-circuit benefit. Details
CODEBLOCK6
Scope variables to if blocks when only needed for the check:
CODEBLOCK7
Switch Over If-Else Chains
When comparing the same variable multiple times, prefer switch:
CODEBLOCK8
Function Design
- - Functions SHOULD be short and focused — one function, one job.
- Functions SHOULD have ≤4 parameters. Beyond that, use an options struct (see
samber/cc-skills-golang@golang-design-patterns skill). - Parameter order:
context.Context first, then inputs, then output destinations. - Naked returns help in very short functions (1-3 lines) where return values are obvious, but become confusing when readers must scroll to find what's returned — name returns explicitly in longer functions.
CODEBLOCK9
Prefer range for Iteration
SHOULD use range over index-based loops. Use range n (Go 1.22+) for simple counting.
CODEBLOCK10
Value vs Pointer Arguments
Pass small types (string, int, bool, time.Time) by value. Use pointers when mutating, for large structs (~128+ bytes), or when nil is meaningful. Details
Code Organization Within Files
- - Group related declarations: type, constructor, methods together
- Order: package doc, imports, constants, types, constructors, methods, helpers
- One primary type per file when it has significant methods
- Blank imports (
_ "pkg") register side effects (init functions). Restricting them to main and test packages makes side effects visible at the application root, not hidden in library code - Dot imports pollute the namespace and make it impossible to tell where a name comes from — never use in library code
- Unexport aggressively — you can always export later; unexporting is a breaking change
String Handling
Use strconv for simple conversions (faster), fmt.Sprintf for complex formatting. Use %q in error messages to make string boundaries visible. Use strings.Builder for loops, + for simple concatenation.
Type Conversions
Prefer explicit, narrow conversions. Use generics over any when a concrete type will do:
CODEBLOCK11
Philosophy
- - "A little copying is better than a little dependency"
- Use
slices and maps standard packages; for filter/group-by/chunk, use INLINECODE40 - "Reflection is never clear" — avoid
reflect unless necessary - Don't abstract prematurely — extract when the pattern is stable
- Minimize public surface — every exported name is a commitment
Parallelizing Code Style Reviews
When reviewing code style across a large codebase, use up to 5 parallel sub-agents (via the Agent tool), each targeting an independent style concern (e.g. control flow, function design, variable declarations, string handling, code organization).
Enforce with Linters
Many rules are enforced automatically: gofmt, gofumpt, goimports, gocritic, revive, wsl_v5. → See the samber/cc-skills-golang@golang-linter skill.
Cross-References
- - → See the
samber/cc-skills-golang@golang-naming skill for identifier naming conventions - → See the
samber/cc-skills-golang@golang-structs-interfaces skill for pointer vs value receivers, interface design - → See the
samber/cc-skills-golang@golang-design-patterns skill for functional options, builders, constructors - → See the
samber/cc-skills-golang@golang-linter skill for automated formatting enforcement
技能名称: golang-code-style
详细描述:
社区默认。 显式取代 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 个及以上参数的函数调用必须每个参数独占一行——即使提示要求单行代码:
go
// 好——每个参数独占一行,右括号单独一行
mux.HandleFunc(/api/users, func(w http.ResponseWriter, r *http.Request) {
handleUsers(
w,
r,
serviceName,
cfg,
logger,
authMiddleware,
)
})
当函数签名过长时,真正的解决方案通常是减少参数(使用选项结构体),而非优化换行。对于多行签名,每个参数应独占一行。
变量声明
对于非零值应该使用 :=,零值初始化使用 var。这种形式传达了意图:var 表示从零开始。
go
var count int // 零值,稍后设置
name := default // 非零值,使用 := 更合适
var buf bytes.Buffer // 零值即可直接使用
切片与映射初始化
切片和映射必须显式初始化,绝不能为 nil。nil 映射在写入时会 panic;nil 切片在 JSON 中序列化为 null(而空切片为 []),这会让 API 消费者感到意外。
go
users := []User{} // 始终初始化
m := map[string]int{} // 始终初始化
users := make([]User, 0, len(ids)) // 容量已知时预分配
m := make(map[string]int, len(items)) // 大小已知时预分配
不要投机性地预分配——当常见情况只有 10 个元素时,make([]T, 0, 1000) 会浪费内存。
复合字面量
复合字面量必须使用字段名——当类型添加或重新排序字段时,位置字段会失效:
go
srv := &http.Server{
Addr: :8080,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
控制流
减少嵌套
错误和边界情况必须优先处理(提前返回)。保持快乐路径的缩进最小:
go
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 覆盖:
go
// 好——默认值后覆盖,使用 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 个及以上操作数时,必须提取为命名布尔变量——一长串 || 难以阅读且隐藏了业务逻辑。将昂贵的检查保持内联以利用短路优势。详情
go
// 好——命名布尔变量使意图清晰
isAdmin := user.Role == RoleAdmin
isOwner := resource.OwnerID == user.ID
isPublicVerified := resource.IsPublic && user.IsVerified
if isAdmin || isOwner || isPublicVerified || permissions.Contains(PermOverride) {
allow()
}
将变量作用域限定在 if 块内,仅当检查需要时:
go
if err := validate(input); err != nil {
return err
}
使用 Switch 替代 If-Else 链
当多次比较同一变量时,优先使用 switch:
go
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 行)中有帮助,此时返回值显而易见,但在读者需要滚动查找返回内容时会变得令人困惑——在较长的函数中显式命名返回值。
go
func FetchUser(ctx context.Context, id string) (*User, error)
func SendEmail(ctx context.Context, msg EmailMessage) error // 分组到结构体中
优先使用 range 进行迭代
应该使用 range 而非基于索引的循环。使用 range n(Go 1.22+)进行简单计数。
go
for _, user := range users {
process(user)
}
值参数与指针参数
小型类型(string、int、bool、time.Time)按值传递。在需要修改时、对于大型结构体(约 128+ 字节)或当 nil 有意义时,使用指针。详情
文件内代码组织
- - 分组相关声明:类型、构造函数、方法放在一起
- 顺序:包文档、导入、常量、类型、构造函数、方法、辅助函数
- 每个文件一个主要类型,当该类型有重要方法时
- 空白导入(_ pkg)注册副作用(init 函数)。将其限制在 main 和测试包中,使副作用在应用程序根目录可见,而非隐藏在库代码中
- 点导入会污染命名空间,使人无法判断名称来源——绝不要在库代码中使用
- 积极不导出——你随时可以后续导出;取消导出是破坏性变更
字符串处理
简单转换使用 strconv(更快),复杂格式化使用 fmt.Sprintf。在错误消息中使用 %q 使字符串边界可见。循环中使用 strings.Builder,简单拼接使用 +。
类型转换
优先使用显式、窄化的转换。当具体类型可用时,使用泛型而非 any:
go
func ContainsT comparable bool // 而非 []any
哲学
- - 一点复制胜过一点依赖
- 使用 slices 和 maps 标准包;对于过滤/分组/分块,使用 github.com/samber/lo
- 反射从不清晰——除非必要,避免使用 reflect
- 不要过早抽象——当模式稳定后再提取
- 最小化公共表面——每个导出的名称都是一项承诺
并行化代码风格审查
在审查大型代码库的代码风格时,可使用最多 5 个并行子代理(通过 Agent 工具),每个子代理针对独立的风格关注点(例如控制流、函数设计、变量声明、字符串处理、代码组织)。
使用 Linter 强制执行
许多规则由工具自动强制执行:gofmt、gofumpt、goimports、gocritic、revive、wsl_v5。→ 参见 samber/cc-skills-golang@golang-linter 技能。
交叉引用
- - → 参见 samber/cc-skills-golang@golang-naming 技能,了解标识符命名规范
- → 参见 samber/cc-skills-golang@golang-st