Skillshub golang-code-style
Golang code style, formatting and conventions. Use when writing code, reviewing style, configuring linters, writing comments, or establishing project standards.
git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/Harmeet10000/skills/golang-code-style" ~/.claude/skills/comeonoliver-skillshub-golang-code-style && rm -rf "$T"
skills/Harmeet10000/skills/golang-code-style/SKILL.mdCommunity default. A company skill that explicitly supersedes
skill takes precedence.samber/cc-skills-golang@golang-code-style
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
elseWhen 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
skill).samber/cc-skills-golang@golang-design-patterns - Parameter order:
first, then inputs, then output destinations.context.Context - 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
rangeSHOULD 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 (
) register side effects (init functions). Restricting them to_ "pkg"
and test packages makes side effects visible at the application root, not hidden in library codemain - 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
andslices
standard packages; for filter/group-by/chunk, usemapsgithub.com/samber/lo - "Reflection is never clear" — avoid
unless necessaryreflect - 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
skill for identifier naming conventionssamber/cc-skills-golang@golang-naming - → See the
skill for pointer vs value receivers, interface designsamber/cc-skills-golang@golang-structs-interfaces - → See the
skill for functional options, builders, constructorssamber/cc-skills-golang@golang-design-patterns - → See the
skill for automated formatting enforcementsamber/cc-skills-golang@golang-linter