Go Mastery
Environment
- go: !
go version 2>/dev/null || echo "NOT INSTALLED"
- golangci-lint: !
golangci-lint version --short 2>/dev/null || echo "NOT INSTALLED"
- govulncheck: !
which govulncheck 2>/dev/null || echo "NOT INSTALLED"
Lint Mode (when user has Go code to review)
Use this mode to systematically lint and fix a Go codebase using the full tooling chain.
Step 1: RUN
Execute the 7-step tooling chain in order. Capture all output:
go build ./... # 1. Compilation errors
go vet ./... # 2. Suspicious constructs
golangci-lint run ./... # 3. Style, bugs, performance, security
govulncheck ./... # 4. Known CVEs in deps
nilaway ./... # 5. Nil pointer dereference detection
deadcode ./... # 6. Unreachable functions
go test -race -count=1 ./... # 7. Data races
Skip any tool that is not installed (check Environment above) and note it in the report.
Step 2: ANALYZE
Parse all tool output and group findings:
| Severity |
Source |
Examples |
| Critical |
govulncheck, race detection |
Known CVEs, data races |
| High |
go vet, nilaway |
Nil derefs, printf mismatches, suspicious constructs |
| Medium |
golangci-lint |
Style violations, inefficient code, unchecked errors |
| Low |
deadcode |
Unused functions (safe to remove) |
Step 3: FIX
For each finding, apply the idiomatic Go fix using the reference patterns below. Prioritize critical and high severity first. Common fix mappings:
- Unchecked error -> add explicit
if err != nil handling
- Nil deref -> add nil guard or restructure control flow
- Race condition -> add mutex, use atomic, or redesign with channels
- Dead code -> remove or gate behind build tag
- CVE -> update dependency with
go get pkg@latest
Step 4: VERIFY
Re-run the full tooling chain to confirm all fixes. Repeat Step 3 for any remaining findings.
Reference Mode (patterns and knowledge)
Production-grade Go patterns from Google, Uber, and the Go team. Updated for Go 1.25.
Quick Decision Table
| Need |
Solution |
Reference |
| Error handling rules |
Return errors, wrap with %w, handle once |
Error Handling |
| Concurrency patterns |
Worker pool, fan-out/fan-in, pipeline, errgroup |
Concurrency |
| Interface design |
Small interfaces, accept interfaces return structs, DI |
Interfaces |
| Generics |
Type constraints, generic data structures, Result[T] |
Generics |
| Testing |
TDD, table-driven, benchmarks, fuzzing, mocking |
Testing |
| Project layout |
cmd/, internal/, pkg/, Dockerfile, Makefile |
Project Structure |
| Production hardening |
Graceful shutdown, rate limiting, health checks, slog |
Production |
| gRPC services |
Protobuf, interceptors, streaming, bufconn testing |
gRPC |
| Static analysis |
govulncheck, nilaway, deadcode, golangci-lint, revive |
Static Analysis |
| Naming, style & linter enforcement |
Naming decision table, linter tiers (MUST/SHOULD/AVOID), production .golangci.yml v2, grep-based review |
Naming & Style |
Core Principles (in order)
- Clarity - purpose and rationale are obvious to the reader
- Simplicity - accomplishes the goal in the simplest way
- Concision - high signal to noise ratio
- Maintainability - easy to modify correctly
- Consistency - matches surrounding codebase
Naming Conventions
// MixedCaps for exported, mixedCaps for unexported
type HTTPClient struct{} // Initialisms: all caps (HTTP, URL, ID, API, JSON)
func ServeHTTP() // Not ServeHttp
// Short variable names for short scopes
for i, v := range items { ... }
func (s *Server) Handle() // Receiver: 1-2 letter abbreviation
// Package names: short, lowercase, no underscores, no plurals
package http // Good
package utils // Bad: meaningless name
package models // Bad: plural
// Don't repeat package name in exported names
package user
func New() *User // Good: user.New()
func NewUser() *User // Bad: user.NewUser()
Import Organization
import (
// Standard library
"context"
"fmt"
"net/http"
// External packages
"github.com/gin-gonic/gin"
"go.uber.org/zap"
// Internal packages
"github.com/myorg/myapp/internal/config"
)
Rules: three groups separated by blank lines. Never rename imports unless conflict. Never dot imports. Blank imports (_ "pkg") only in main or test files.
Pointer vs Value Receivers
Use pointer receiver (*T) |
Use value receiver (T) |
| Method mutates the receiver |
Method does not mutate |
| Struct is large |
Struct is small (few fields, no pointers) |
| Consistency: other methods use pointer |
Type is a map, func, or chan |
| Must satisfy interface with pointer methods |
Basic types (int, string) |
Rule: don't mix. Pick one style per type.
Slices and Maps
// Prefer nil slice (behaves like empty for most ops)
var s []string // Good: nil, len=0, JSON marshals to null
s := []string{} // Only when you need JSON [] instead of null
// Preallocate when size is known
results := make([]Result, 0, len(items))
// Copy at API boundaries to prevent mutation
func (s *Store) GetIDs() []string {
return slices.Clone(s.ids)
}
// Use standard library (Go 1.21+)
slices.Sort(items)
slices.Contains(items, target)
maps.Clone(m)
maps.Keys(m)
Modern Go Features
// Range-over-func iterators (Go 1.23+)
func All[K, V any](m map[K]V) iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
for k, v := range m {
if !yield(k, v) { return }
}
}
}
// Tool directives in go.mod (Go 1.24+)
// tool (
// golang.org/x/tools/cmd/stringer
// github.com/golang/mock/mockgen
// )
// Typed atomics (Go 1.19+)
var count atomic.Int64
count.Add(1)
// errors.Join (Go 1.20+)
err := errors.Join(err1, err2, err3)
// slog structured logging (Go 1.21+)
slog.Info("request", "method", r.Method, "path", r.URL.Path, "status", status)
Go Idioms (Quick Reference)
| Idiom |
Description |
| Accept interfaces, return structs |
Functions take interface params, return concrete types |
| Errors are values |
Treat errors as data, not exceptions |
| Make the zero value useful |
Types work without explicit init |
| A little copying > a little dependency |
Avoid unnecessary deps |
| Return early |
Handle errors first, keep happy path unindented |
| Don't communicate by sharing memory |
Use channels for goroutine coordination |
| Prefer synchronous functions |
Let callers add concurrency |
| Channel buffer: 0 or 1 |
Justify anything larger |
| Handle errors once |
Don't log AND return an error |
Anti-Patterns
// Bad: naked returns in long functions
func process() (result int, err error) {
// ... 50 lines ...
return // What is being returned?
}
// Bad: panic for control flow (use only for truly unrecoverable states)
func GetUser(id string) *User {
user, err := db.Find(id)
if err != nil { panic(err) }
return user
}
// Bad: context in struct field
type Request struct {
ctx context.Context // Context should be first param
ID string
}
// Bad: ignoring errors silently
result, _ := doSomething()
// Bad: error strings with capital or punctuation
fmt.Errorf("Failed to connect.") // Wrong
fmt.Errorf("connect to db: %w", err) // Correct
Tooling
# Essential (run all before merge)
go vet ./... # Compiler-level static analysis
golangci-lint run ./... # Comprehensive linting (50+ linters)
go test -race ./... # Race detection
go test -cover -coverprofile=coverage.out ./...
# Deep static analysis (run during code review)
govulncheck ./... # CVE vulnerability scanner (Go team official)
nilaway ./... # Nil pointer dereference detection (Uber)
deadcode ./... # Find unreachable functions (Go team official)
# Build
CGO_ENABLED=0 go build -o app ./cmd/server
go build -ldflags "-X main.version=1.0.0" ./cmd/server
# Module
go mod tidy # Clean dependencies
go mod verify # Verify checksums
Code Review Checklist (tools to run)
| Step |
Tool |
What it catches |
| 1 |
go build ./... |
Compilation errors |
| 2 |
go vet ./... |
Suspicious constructs, printf mismatches |
| 3 |
golangci-lint run ./... |
Style, bugs, performance, security, naming (via revive) |
| 4 |
govulncheck ./... |
Known CVEs in dependencies and stdlib |
| 5 |
nilaway ./... |
Nil pointer panics before runtime |
| 6 |
deadcode ./... |
Unreachable/unused functions |
| 7 |
go test -race ./... |
Data races |
See Static Analysis reference for install, config, and suppression patterns.
See Naming & Style for the linter-tier decision (MUST / SHOULD / AVOID), revive rule catalog, and a copy-pasteable production-grade config.
golangci-lint Configuration (.golangci.yml — v2 syntax, production-grade)
A minimal-but-real config. The full annotated version (with exclusion blocks for gochecknoglobals legitimate-globals, generated proto code, ldflag build vars, Lua scripts, and tier-3 linter warnings) lives in Naming & Style §3.
version: "2"
run:
timeout: 5m
modules-download-mode: readonly
linters:
enable:
# correctness
- errcheck
- govet
- staticcheck # absorbs gosimple in v2
- unused
- ineffassign
- unconvert
# concurrency & race conditions
- copyloopvar # loop variable capture safety net
# resource leaks
- bodyclose # unclosed HTTP response bodies
- noctx # HTTP requests without context
# security
- gosec
# bugs & correctness
- durationcheck # time.Duration * time.Duration = wrong
- reassign # mutating package-level vars
- wastedassign
- musttag # missing struct tags
- protogetter # proto getter for nil safety
# style & convention
- misspell
- gocritic
- revive # ← the naming/style enforcer (see §revive below)
# globals & init hygiene (Tier 2 — needs exclusions below)
- gochecknoglobals
- gochecknoinits
- interfacebloat
- predeclared
settings:
errcheck:
check-type-assertions: true
check-blank: true
govet:
enable-all: true # all analyzers (shadow, copylocks, loopclosure, ...)
disable:
- fieldalignment # too noisy, micro-optimization
gosec:
excludes:
- G104 # errcheck covers
- G304 # file path from variable — expected in CLI
severity: medium
confidence: medium
gocritic:
enabled-tags: [diagnostic, performance]
disabled-checks: [hugeParam]
revive:
# 21-rule production set — names + errors + context + idioms.
# Each rule documented at references/naming-and-style.md §1.
rules:
# Naming
- {name: var-naming}
- {name: receiver-naming}
- {name: error-naming}
- {name: time-naming}
- {name: package-comments}
- {name: exported}
# Errors
- {name: error-return}
- {name: error-strings}
- {name: errorf}
# Context
- {name: context-as-argument}
- {name: context-keys-type}
# Imports
- {name: blank-imports}
# Control flow
- {name: if-return}
- {name: indent-error-flow}
- {name: superfluous-else}
- {name: unreachable-code}
- {name: empty-block}
# Idioms
- {name: increment-decrement}
- {name: range}
- {name: unexported-return}
- {name: defer}
musttag:
functions:
- {name: encoding/json.Marshal, tag: json}
- {name: encoding/json.Unmarshal, tag: json}
exclusions:
rules:
- path: _test\.go
linters: [errcheck, gocritic, gosec, musttag, gochecknoglobals]
- path: pkg/ # generated proto code
linters: [musttag, protogetter, revive, gochecknoglobals]
# gochecknoglobals — legitimate Go patterns
- linters: [gochecknoglobals]
text: "^(Version|Commit|BuildDate|BuildTime|GitSHA) is a global variable$"
- linters: [gochecknoglobals]
text: "^Err[A-Z]" # sentinel errors
# Formatters live in their own block in v2 (not inside `linters`)
formatters:
enable:
- gofmt
- goimports
issues:
max-issues-per-linter: 50
max-same-issues: 5
v2 canonical structure: linters.settings, linters.exclusions.rules, and a separate top-level formatters block. Legacy top-level linters-settings: and issues.exclude-rules: still work at runtime but fail golangci-lint config verify. Use canonical form for clean CI.
Linter tier discipline — full rationale at Naming & Style §2:
- Tier 1 MUST (very low FP rate):
errcheck, govet, staticcheck, unused, ineffassign, unconvert, bodyclose, noctx, copyloopvar, durationcheck, reassign, wastedassign, musttag, protogetter, gosec, gocritic, misspell, revive.
- Tier 2 SHOULD with exclusions:
gochecknoglobals, gochecknoinits, interfacebloat, predeclared.
- Tier 3 AVOID without strong reason:
tagliatelle (defaults to camelCase — fights snake_case wire formats), varnamelen (fights Go's "short scope short name" idiom), wsl, lll, funlen, gocyclo (use gocognit instead), nlreturn.
Migrating from v1 config
If your repo still uses v1 syntax, three fixes:
- Remove
gosimple (merged into staticcheck in v2)
- Move
gofmt and goimports from linters: into a top-level formatters: block
- Replace
govet.check-shadowing: true with govet.enable-all: true and disable fieldalignment
Or run golangci-lint migrate for automatic translation.
1---2name: aio-golang-mastery3description: Write, review, and lint Go code. Lint mode runs go build, go vet, golangci-lint, govulncheck, nilaway, deadcode, and race detection (race detector), then applies idiomatic fixes. Reference mode covers concurrency, error handling, generics, testing, gRPC, and production hardening. Use when asked to lint golang, run a go lint pipeline, review go code quality, do go static analysis, write idiomatic go, or apply go best practices.4---56# Go Mastery78## Environment9- go: !`go version 2>/dev/null || echo "NOT INSTALLED"`10- golangci-lint: !`golangci-lint version --short 2>/dev/null || echo "NOT INSTALLED"`11- govulncheck: !`which govulncheck 2>/dev/null || echo "NOT INSTALLED"`1213## Lint Mode (when user has Go code to review)1415Use this mode to systematically lint and fix a Go codebase using the full tooling chain.1617### Step 1: RUN18Execute the 7-step tooling chain in order. Capture all output:19```bash20go build ./... # 1. Compilation errors21go vet ./... # 2. Suspicious constructs22golangci-lint run ./... # 3. Style, bugs, performance, security23govulncheck ./... # 4. Known CVEs in deps24nilaway ./... # 5. Nil pointer dereference detection25deadcode ./... # 6. Unreachable functions26go test -race -count=1 ./... # 7. Data races27```28Skip any tool that is not installed (check Environment above) and note it in the report.2930### Step 2: ANALYZE31Parse all tool output and group findings:3233| Severity | Source | Examples |34|----------|--------|----------|35| **Critical** | govulncheck, race detection | Known CVEs, data races |36| **High** | go vet, nilaway | Nil derefs, printf mismatches, suspicious constructs |37| **Medium** | golangci-lint | Style violations, inefficient code, unchecked errors |38| **Low** | deadcode | Unused functions (safe to remove) |3940### Step 3: FIX41For each finding, apply the idiomatic Go fix using the reference patterns below. Prioritize critical and high severity first. Common fix mappings:42- Unchecked error -> add explicit `if err != nil` handling43- Nil deref -> add nil guard or restructure control flow44- Race condition -> add mutex, use atomic, or redesign with channels45- Dead code -> remove or gate behind build tag46- CVE -> update dependency with `go get pkg@latest`4748### Step 4: VERIFY49Re-run the full tooling chain to confirm all fixes. Repeat Step 3 for any remaining findings.5051---5253## Reference Mode (patterns and knowledge)5455Production-grade Go patterns from Google, Uber, and the Go team. Updated for Go 1.25.5657## Quick Decision Table5859| Need | Solution | Reference |60|------|----------|-----------|61| Error handling rules | Return errors, wrap with %w, handle once | [Error Handling](references/error-handling.md) |62| Concurrency patterns | Worker pool, fan-out/fan-in, pipeline, errgroup | [Concurrency](references/concurrency.md) |63| Interface design | Small interfaces, accept interfaces return structs, DI | [Interfaces](references/interfaces.md) |64| Generics | Type constraints, generic data structures, Result[T] | [Generics](references/generics.md) |65| Testing | TDD, table-driven, benchmarks, fuzzing, mocking | [Testing](references/testing.md) |66| Project layout | cmd/, internal/, pkg/, Dockerfile, Makefile | [Project Structure](references/project-structure.md) |67| Production hardening | Graceful shutdown, rate limiting, health checks, slog | [Production](references/production.md) |68| gRPC services | Protobuf, interceptors, streaming, bufconn testing | [gRPC](references/grpc.md) |69| Static analysis | govulncheck, nilaway, deadcode, golangci-lint, revive | [Static Analysis](references/static-analysis.md) |70| Naming, style & linter enforcement | Naming decision table, linter tiers (MUST/SHOULD/AVOID), production `.golangci.yml` v2, grep-based review | [Naming & Style](references/naming-and-style.md) |7172## Core Principles (in order)73741. **Clarity** - purpose and rationale are obvious to the reader752. **Simplicity** - accomplishes the goal in the simplest way763. **Concision** - high signal to noise ratio774. **Maintainability** - easy to modify correctly785. **Consistency** - matches surrounding codebase7980## Naming Conventions8182```go83// MixedCaps for exported, mixedCaps for unexported84type HTTPClient struct{} // Initialisms: all caps (HTTP, URL, ID, API, JSON)85func ServeHTTP() // Not ServeHttp8687// Short variable names for short scopes88for i, v := range items { ... }89func (s *Server) Handle() // Receiver: 1-2 letter abbreviation9091// Package names: short, lowercase, no underscores, no plurals92package http // Good93package utils // Bad: meaningless name94package models // Bad: plural9596// Don't repeat package name in exported names97package user98func New() *User // Good: user.New()99func NewUser() *User // Bad: user.NewUser()100```101102## Import Organization103104```go105import (106 // Standard library107 "context"108 "fmt"109 "net/http"110111 // External packages112 "github.com/gin-gonic/gin"113 "go.uber.org/zap"114115 // Internal packages116 "github.com/myorg/myapp/internal/config"117)118```119120Rules: three groups separated by blank lines. Never rename imports unless conflict. Never dot imports. Blank imports (`_ "pkg"`) only in main or test files.121122## Pointer vs Value Receivers123124| Use pointer receiver (`*T`) | Use value receiver (`T`) |125|----------------------------|------------------------|126| Method mutates the receiver | Method does not mutate |127| Struct is large | Struct is small (few fields, no pointers) |128| Consistency: other methods use pointer | Type is a map, func, or chan |129| Must satisfy interface with pointer methods | Basic types (int, string) |130131**Rule: don't mix.** Pick one style per type.132133## Slices and Maps134135```go136// Prefer nil slice (behaves like empty for most ops)137var s []string // Good: nil, len=0, JSON marshals to null138s := []string{} // Only when you need JSON [] instead of null139140// Preallocate when size is known141results := make([]Result, 0, len(items))142143// Copy at API boundaries to prevent mutation144func (s *Store) GetIDs() []string {145 return slices.Clone(s.ids)146}147148// Use standard library (Go 1.21+)149slices.Sort(items)150slices.Contains(items, target)151maps.Clone(m)152maps.Keys(m)153```154155## Modern Go Features156157```go158// Range-over-func iterators (Go 1.23+)159func All[K, V any](m map[K]V) iter.Seq2[K, V] {160 return func(yield func(K, V) bool) {161 for k, v := range m {162 if !yield(k, v) { return }163 }164 }165}166167// Tool directives in go.mod (Go 1.24+)168// tool (169// golang.org/x/tools/cmd/stringer170// github.com/golang/mock/mockgen171// )172173// Typed atomics (Go 1.19+)174var count atomic.Int64175count.Add(1)176177// errors.Join (Go 1.20+)178err := errors.Join(err1, err2, err3)179180// slog structured logging (Go 1.21+)181slog.Info("request", "method", r.Method, "path", r.URL.Path, "status", status)182```183184## Go Idioms (Quick Reference)185186| Idiom | Description |187|-------|-------------|188| Accept interfaces, return structs | Functions take interface params, return concrete types |189| Errors are values | Treat errors as data, not exceptions |190| Make the zero value useful | Types work without explicit init |191| A little copying > a little dependency | Avoid unnecessary deps |192| Return early | Handle errors first, keep happy path unindented |193| Don't communicate by sharing memory | Use channels for goroutine coordination |194| Prefer synchronous functions | Let callers add concurrency |195| Channel buffer: 0 or 1 | Justify anything larger |196| Handle errors once | Don't log AND return an error |197198## Anti-Patterns199200```go201// Bad: naked returns in long functions202func process() (result int, err error) {203 // ... 50 lines ...204 return // What is being returned?205}206207// Bad: panic for control flow (use only for truly unrecoverable states)208func GetUser(id string) *User {209 user, err := db.Find(id)210 if err != nil { panic(err) }211 return user212}213214// Bad: context in struct field215type Request struct {216 ctx context.Context // Context should be first param217 ID string218}219220// Bad: ignoring errors silently221result, _ := doSomething()222223// Bad: error strings with capital or punctuation224fmt.Errorf("Failed to connect.") // Wrong225fmt.Errorf("connect to db: %w", err) // Correct226```227228## Tooling229230```bash231# Essential (run all before merge)232go vet ./... # Compiler-level static analysis233golangci-lint run ./... # Comprehensive linting (50+ linters)234go test -race ./... # Race detection235go test -cover -coverprofile=coverage.out ./...236237# Deep static analysis (run during code review)238govulncheck ./... # CVE vulnerability scanner (Go team official)239nilaway ./... # Nil pointer dereference detection (Uber)240deadcode ./... # Find unreachable functions (Go team official)241242# Build243CGO_ENABLED=0 go build -o app ./cmd/server244go build -ldflags "-X main.version=1.0.0" ./cmd/server245246# Module247go mod tidy # Clean dependencies248go mod verify # Verify checksums249```250251### Code Review Checklist (tools to run)252253| Step | Tool | What it catches |254|------|------|-----------------|255| 1 | `go build ./...` | Compilation errors |256| 2 | `go vet ./...` | Suspicious constructs, printf mismatches |257| 3 | `golangci-lint run ./...` | Style, bugs, performance, security, naming (via `revive`) |258| 4 | `govulncheck ./...` | Known CVEs in dependencies and stdlib |259| 5 | `nilaway ./...` | Nil pointer panics before runtime |260| 6 | `deadcode ./...` | Unreachable/unused functions |261| 7 | `go test -race ./...` | Data races |262263See [Static Analysis reference](references/static-analysis.md) for install, config, and suppression patterns.264See [Naming & Style](references/naming-and-style.md) for the linter-tier decision (MUST / SHOULD / AVOID), `revive` rule catalog, and a copy-pasteable production-grade config.265266### golangci-lint Configuration (.golangci.yml — v2 syntax, production-grade)267268A minimal-but-real config. The full annotated version (with exclusion blocks for `gochecknoglobals` legitimate-globals, generated proto code, ldflag build vars, Lua scripts, and tier-3 linter warnings) lives in [Naming & Style §3](references/naming-and-style.md#3-production-grade-golangciyml-v2-syntax).269270```yaml271version: "2"272273run:274 timeout: 5m275 modules-download-mode: readonly276277linters:278 enable:279 # correctness280 - errcheck281 - govet282 - staticcheck # absorbs gosimple in v2283 - unused284 - ineffassign285 - unconvert286287 # concurrency & race conditions288 - copyloopvar # loop variable capture safety net289290 # resource leaks291 - bodyclose # unclosed HTTP response bodies292 - noctx # HTTP requests without context293294 # security295 - gosec296297 # bugs & correctness298 - durationcheck # time.Duration * time.Duration = wrong299 - reassign # mutating package-level vars300 - wastedassign301 - musttag # missing struct tags302 - protogetter # proto getter for nil safety303304 # style & convention305 - misspell306 - gocritic307 - revive # ← the naming/style enforcer (see §revive below)308309 # globals & init hygiene (Tier 2 — needs exclusions below)310 - gochecknoglobals311 - gochecknoinits312 - interfacebloat313 - predeclared314315 settings:316 errcheck:317 check-type-assertions: true318 check-blank: true319320 govet:321 enable-all: true # all analyzers (shadow, copylocks, loopclosure, ...)322 disable:323 - fieldalignment # too noisy, micro-optimization324325 gosec:326 excludes:327 - G104 # errcheck covers328 - G304 # file path from variable — expected in CLI329 severity: medium330 confidence: medium331332 gocritic:333 enabled-tags: [diagnostic, performance]334 disabled-checks: [hugeParam]335336 revive:337 # 21-rule production set — names + errors + context + idioms.338 # Each rule documented at references/naming-and-style.md §1.339 rules:340 # Naming341 - {name: var-naming}342 - {name: receiver-naming}343 - {name: error-naming}344 - {name: time-naming}345 - {name: package-comments}346 - {name: exported}347 # Errors348 - {name: error-return}349 - {name: error-strings}350 - {name: errorf}351 # Context352 - {name: context-as-argument}353 - {name: context-keys-type}354 # Imports355 - {name: blank-imports}356 # Control flow357 - {name: if-return}358 - {name: indent-error-flow}359 - {name: superfluous-else}360 - {name: unreachable-code}361 - {name: empty-block}362 # Idioms363 - {name: increment-decrement}364 - {name: range}365 - {name: unexported-return}366 - {name: defer}367368 musttag:369 functions:370 - {name: encoding/json.Marshal, tag: json}371 - {name: encoding/json.Unmarshal, tag: json}372373 exclusions:374 rules:375 - path: _test\.go376 linters: [errcheck, gocritic, gosec, musttag, gochecknoglobals]377 - path: pkg/ # generated proto code378 linters: [musttag, protogetter, revive, gochecknoglobals]379 # gochecknoglobals — legitimate Go patterns380 - linters: [gochecknoglobals]381 text: "^(Version|Commit|BuildDate|BuildTime|GitSHA) is a global variable$"382 - linters: [gochecknoglobals]383 text: "^Err[A-Z]" # sentinel errors384385# Formatters live in their own block in v2 (not inside `linters`)386formatters:387 enable:388 - gofmt389 - goimports390391issues:392 max-issues-per-linter: 50393 max-same-issues: 5394```395396> **v2 canonical structure**: `linters.settings`, `linters.exclusions.rules`, and a separate top-level `formatters` block. Legacy top-level `linters-settings:` and `issues.exclude-rules:` still work at runtime but fail `golangci-lint config verify`. Use canonical form for clean CI.397398**Linter tier discipline** — full rationale at [Naming & Style §2](references/naming-and-style.md#2-linter-tiers--what-to-enable):399400- **Tier 1 MUST** (very low FP rate): `errcheck`, `govet`, `staticcheck`, `unused`, `ineffassign`, `unconvert`, `bodyclose`, `noctx`, `copyloopvar`, `durationcheck`, `reassign`, `wastedassign`, `musttag`, `protogetter`, `gosec`, `gocritic`, `misspell`, `revive`.401- **Tier 2 SHOULD with exclusions**: `gochecknoglobals`, `gochecknoinits`, `interfacebloat`, `predeclared`.402- **Tier 3 AVOID without strong reason**: `tagliatelle` (defaults to camelCase — fights snake_case wire formats), `varnamelen` (fights Go's "short scope short name" idiom), `wsl`, `lll`, `funlen`, `gocyclo` (use `gocognit` instead), `nlreturn`.403404### Migrating from v1 config405406If your repo still uses v1 syntax, three fixes:4071. Remove `gosimple` (merged into `staticcheck` in v2)4082. Move `gofmt` and `goimports` from `linters:` into a top-level `formatters:` block4093. Replace `govet.check-shadowing: true` with `govet.enable-all: true` and disable `fieldalignment`410411Or run `golangci-lint migrate` for automatic translation.