| name | aio-golang-mastery |
| description | 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.
|
| when_to_use | go code, golang, lint go, review go, go best practices, concurrency, goroutines, channels, error handling, gRPC, race condition, generics, idiomatic go, go testing, govulncheck, nilaway, deadcode, golangci-lint, go lint pipeline, go code quality, go static analysis, go review, lint golang, race detector, golangci, idiomatic golang |
| argument-hint | [lint | review | reference] [optional path] |
| effort | high |
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 ./...
go vet ./...
golangci-lint run ./...
govulncheck ./...
nilaway ./...
deadcode ./...
go test -race -count=1 ./...
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
type HTTPClient struct{}
func ServeHTTP()
for i, v := range items { ... }
func (s *Server) Handle()
package http
package utils
package models
package user
func New() *User
func NewUser() *User
Import Organization
import (
"context"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"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
var s []string
s := []string{}
results := make([]Result, 0, len(items))
func (s *Store) GetIDs() []string {
return slices.Clone(s.ids)
}
slices.Sort(items)
slices.Contains(items, target)
maps.Clone(m)
maps.Keys(m)
Modern Go Features
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 }
}
}
}
var count atomic.Int64
count.Add(1)
err := errors.Join(err1, err2, err3)
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
func process() (result int, err error) {
return
}
func GetUser(id string) *User {
user, err := db.Find(id)
if err != nil { panic(err) }
return user
}
type Request struct {
ctx context.Context
ID string
}
result, _ := doSomething()
fmt.Errorf("Failed to connect.")
fmt.Errorf("connect to db: %w", err)
Tooling
go vet ./...
golangci-lint run ./...
go test -race ./...
go test -cover -coverprofile=coverage.out ./...
govulncheck ./...
nilaway ./...
deadcode ./...
CGO_ENABLED=0 go build -o app ./cmd/server
go build -ldflags "-X main.version=1.0.0" ./cmd/server
go mod tidy
go mod verify
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:
- errcheck
- govet
- staticcheck
- unused
- ineffassign
- unconvert
- copyloopvar
- bodyclose
- noctx
- gosec
- durationcheck
- reassign
- wastedassign
- musttag
- protogetter
- misspell
- gocritic
- revive
- gochecknoglobals
- gochecknoinits
- interfacebloat
- predeclared
settings:
errcheck:
check-type-assertions: true
check-blank: true
govet:
enable-all: true
disable:
- fieldalignment
gosec:
excludes:
- G104
- G304
severity: medium
confidence: medium
gocritic:
enabled-tags: [diagnostic, performance]
disabled-checks: [hugeParam]
revive:
rules:
- {name: var-naming}
- {name: receiver-naming}
- {name: error-naming}
- {name: time-naming}
- {name: package-comments}
- {name: exported}
- {name: error-return}
- {name: error-strings}
- {name: errorf}
- {name: context-as-argument}
- {name: context-keys-type}
- {name: blank-imports}
- {name: if-return}
- {name: indent-error-flow}
- {name: superfluous-else}
- {name: unreachable-code}
- {name: empty-block}
- {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/
linters: [musttag, protogetter, revive, gochecknoglobals]
- linters: [gochecknoglobals]
text: "^(Version|Commit|BuildDate|BuildTime|GitSHA) is a global variable$"
- linters: [gochecknoglobals]
text: "^Err[A-Z]"
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.