go-code-style — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited go-code-style (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
gofmt handles the mechanical layout. This skill covers the decisions a formatter cannot make: where to break complexity, when to invert an if, when a switch beats an else if chain, and how to pick value vs pointer arguments. The rule of thumb is the Go Proverb: clear is better than clever.
if body ends in return/break/continue.Apply in this order when two principles collide.
| Priority | Question | Beats |
|---|---|---|
| 1. Clarity | Can a reader understand intent without extra context? | everything else |
| 2. Simplicity | Is this the simplest expression of the idea? | concision, consistency |
| 3. Concision | Does every line earn its place? | consistency |
| 4. Maintainability | Will this be safe to modify? | consistency |
| 5. Consistency | Does it match nearby code? | — |
A "consistent" piece of bad code is still bad code. Clarity wins.
Read references/formatting-and-layout.md for line-breaking, multi-line signatures, file/declaration order.
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
}elseWhen the if body unconditionally exits (return/break/continue), drop the else. For assignments, prefer default-then-override:
// Good
lvl := slog.LevelInfo
if debug {
lvl = slog.LevelDebug
}When all branches compare the same value, switch makes intent explicit and exhaustive can verify completeness:
switch status {
case StatusActive:
activate()
case StatusInactive:
deactivate()
case StatusPaused:
pause()
default:
panic(fmt.Sprintf("unexpected status: %d", status))
}A tagless switch replaces a chain of unrelated if/else if conditions — first matching case wins.
When an if has 3+ operands, hoist into named booleans so the names document the business rule:
isAdmin := user.Role == RoleAdmin
isOwner := resource.OwnerID == user.ID
if isAdmin || isOwner || permissions.Has(PermOverride) {
allow()
}Read references/control-flow.md for tagless switch, guard clauses, and labelled break/continue.
Use := for non-zero initializers, var when the zero value is the start.
var count int // start at 0
name := "default" // non-zero
var buf bytes.Buffer // zero value is ready to useA nil map panics on write; a nil slice JSON-encodes to null (a UX surprise for API consumers).
users := []User{} // explicit empty
m := map[string]int{} // explicit empty
users = make([]User, 0, len(ids)) // preallocate when size is known
m = make(map[string]int, len(items)) // preallocate map bucketsDo not speculatively preallocate large capacities — make([]T, 0, 1000) wastes memory when the common case is 10.
srv := &http.Server{
Addr: ":8080",
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}Positional fields break the moment the type adds or reorders a field.
ctx context.Context first, then inputs, then output destinations.range n since Go 1.22).*string, *int parameters add indirection with no real saving. sync.Mutex and types embedding one are never copied — go vet copylocks catches it.| Anti-pattern | Why it hurts | Do this instead |
|---|---|---|
Deeply nested if chains | Happy path scrolls off-screen | Invert, return early |
if ok { return a } else { return b } | else is dead weight | Drop the else |
if x == A else if x == B else if x == C | Reader has to verify all comparisons share x | switch x { ... } |
| Positional struct literal | Breaks silently on field reorder | Named fields |
| Nil map write | Runtime panic | make(...) or map literal |
*string, *int parameter to "save copy" | Adds indirection, no real saving | Pass value |
| Long parameter lists | Hard to call, hard to extend | Options struct or WithXxx opts |
gofmt -l . produces no output and goimports -l . is clean.if ... return; else ... in modified code.make with a sensible capacity.ctx context.Context is the first parameter on every function that takes one.golangci-lint run passes with gocritic, revive, and gocyclo enabled.These rules are largely mechanical and a linter will catch them in CI:
gofmt, gofumpt, goimports — formatting.gocritic, revive — style heuristics, including if-return, early-return.gocyclo, funlen — function complexity / length.wsl_v5 — whitespace lines for separation between blocks.Add to .golangci.yml and run golangci-lint run in CI.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.