Build, extend, and review Go CLIs with Cobra + Viper — flags, config, exit codes, signals, completion, testing.
Documents
golang-code-style
Try itHand-judged Go style guidance on line breaks, declarations, control flow, and clarity beyond what linters enforce.
What it does
Covers style rules that need human judgment: breaking lines at semantic boundaries, choosing between := and var, initializing slices and maps explicitly, using named composite literals, reducing nesting with early returns, dropping unnecessary else, extracting complex conditions into named booleans, and keeping functions short with ≤4 parameters. Use it when writing or reviewing Go code, settling style debates, or establishing project coding standards. Mechanical formatting is delegated to linters; naming, doc comments, and design patterns live in sibling skills.
When to use it
- Writing or reviewing Go code for clarity
- Settling style disagreements in a Go project
- Establishing coding standards for a team
- Splitting a large codebase review across parallel style passes
The skill document
Community default. A company skill that explicitly supersedes
samber/cc-skills-golang@golang-code-styleskill 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:
// 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,
)
})
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."
var count int // zero value, set later
name := "default" // non-zero, := is appropriate
var buf bytes.Buffer // zero value is ready to use
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.
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
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:
srv := &http.Server{
Addr: ":8080",
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
Control Flow
Reduce Nesting
Errors and edge cases MUST be handled first (early return). Keep the happy path at minimal indentation:
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
}
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:
// 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
}
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
// 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()
}
Scope variables to if blocks when only needed for the check:
if err := validate(input); err != nil {
return err
}
Switch Over If-Else Chains
When comparing the same variable multiple times, prefer switch:
switch status {
case StatusActive:
activate()
case StatusInactive:
deactivate()
default:
panic(fmt.Sprintf("unexpected status: %d", status))
}
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-patternsskill). - Parameter order:
context.Contextfirst, 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.
func FetchUser(ctx context.Context, id string) (*User, error)
func SendEmail(ctx context.Context, msg EmailMessage) error // grouped into struct
Prefer range for Iteration
SHOULD use range over index-based loops. Use range n (Go 1.22+) for simple counting.
for _, user := range users {
process(user)
}
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 tomainand 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:
func Contains[T comparable](slice []T, target T) bool // not []any
Philosophy
- "A little copying is better than a little dependency"
- Use
slicesandmapsstandard packages; for filter/group-by/chunk, usegithub.com/samber/lo - "Reflection is never clear" — avoid
reflectunless 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-lint skill.
Cross-References
- → See the
samber/cc-skills-golang@golang-namingskill for identifier naming conventions - → See the
samber/cc-skills-golang@golang-structs-interfacesskill for pointer vs value receivers, interface design - → See the
samber/cc-skills-golang@golang-design-patternsskill for functional options, builders, constructors - → See the
samber/cc-skills-golang@golang-lintskill for automated formatting enforcement - → See
samber/cc-skills-golang@golang-continuous-integrationskill for automated AI-driven code review in CI using these guidelines
Related skills
Golang semantic code intelligence via `gopls`, the official Go language server — go-to-definition, find references, call/implementation hierarchy, workspace symbol search, package API discovery, diagnostics, safe rename, refactors (extract/inline/fill/rewrite code actions), formatting, and generated tests. Reaches an agent via gopls's own MCP server (`go_*` tools), Claude Code's native `LSP` tool, or the `gopls` CLI. Use when navigating or refactoring Go code — jumping to a definition, finding call sites before a rename, understanding a file's or package's dependencies, running diagnostics after an edit, or extracting/inlining/renaming. Not for the published ecosystem — packages not in your `go.mod`, versions, licenses, importers — → See `samber/cc-skills-golang@golang-pkg-go-dev` skill (`godig`). Not for a whole-tree vulnerability audit → See `samber/cc-skills-golang@golang-security` skill (`govulncheck`).
Golang skills orchestrator — always active on any Golang coding, review, debug, or setup task. Reads the task context and loads the most relevant skills from samber/cc-skills-golang, often multiple at once: writing a gRPC service loads golang-grpc + golang-testing + golang-error-handling; debugging a panic loads golang-troubleshooting + golang-safety; auditing security loads golang-security + golang-lint + golang-safety. Also: disambiguates competing clusters when two skills seem to overlap (performance vs benchmark vs troubleshooting, samber/lo vs mo vs ro, DI cluster, safety vs security), and configures the project's agent-config file (CLAUDE.md, AGENTS.md, GEMINI.md, Cursor rules, or Copilot instructions) to force-trigger skills in a project (/golang-how-to configure).
Systematic root-cause debugging for Go programs, from reproduction to verification.
Measure Go performance with statistical rigor — write benchmarks, profile hot paths, and detect regressions before shipping.
Generate production-ready Go tests with table-driven patterns, goleak, fuzzing, and CI-ready structure.