golang

SKILL.md

ABOUTME: Complete Go development guide - code, design, concurrency, performance, review

ABOUTME: Modern Go (1.22-1.26): error layering, stdlib router, Green Tea GC, modern stdlib prefs

Go Development

Quick Reference

gofmt -w . && goimports -w . && go fix ./... && go vet ./...
go test ./... && go test -race ./... && go test -cover ./...
go build -pgo=cpu.pprof -o bin/app ./cmd/app
golangci-lint run

See also: _AST_GREP.md, _PATTERNS.md, source-control


Pre-Commit Verification (MANDATORY)

gofmt -w .                    # Fix formatting FIRST (sqlc/codegen can misalign)
go fix ./... && go fix ./...  # 1.26+: 25 modernizers (run twice for synergistic fixes)
go vet ./...                  # Static analysis
go build ./...                # Compilation check
go test -race -count=1 ./...  # Tests with race detector
golangci-lint run             # Lint

Why gofmt before build: Code generators (sqlc, protoc) may produce code gofmt disagrees with. Always run gofmt -w after regeneration and before commit.

go fix in 1.26+: Completely rewritten with 25 modernizers on the go/analysis framework. Auto-rewrites: interface{}any, sort.Sliceslices.Sort, wg.Add+gowg.Go, errors.Aserrors.AsType[T], omitemptyomitzero, C-style loops→range int, and more. Version-gated by go.mod directive. Run twice (synergistic fixes). Preview with go fix -diff ./.... List fixers: go tool fix help.


Modern Go (1.22+)

1.22: Loop var fix (each iteration owns its variable). Range over int: for i := range 10. Stdlib router: mux.HandleFunc("GET /api/v1/feed/{id}", h) + r.PathValue("id").

1.23: iter.Seq[T] lazy sequences (use sparingly). time.Tick now GC-safe (no more leak). maps.Keys, slices.Collect, slices.Sorted.

1.24: t.Context() auto-cancelled test context. omitzero JSON tag (fixes omitempty for Duration/structs). b.Loop() for benchmarks. strings.SplitSeq lazy iteration (avoids slice alloc).

1.25: Container-aware GOMAXPROCS, Green Tea GC (experimental), sync.WaitGroup.Go().

1.26: Green Tea GC default ON (10-40% lower overhead), new(42), errors.AsType[T], self-referential generics, ~30% faster cgo, go fix rewritten (25 modernizers). Goroutine leak detection (/debug/pprof/goroutineleak) requires GOEXPERIMENT=goroutineleakprofile.


Code Conventions

Formatting: gofmt/goimports: NON-NEGOTIABLE.

Naming: Short vars in funcs (i, c), descriptive at pkg level (ErrNotFound). Receivers 1-2 letter (c *Client). Initialisms all-caps or all-lower (ServeHTTP, appID). Packages lowercase singular.

Errors: Always handle (never _). Wrap: fmt.Errorf("decompress %v: %w", name, err). Lowercase, no punctuation, guard clauses. Never wrap io.EOF (callers use ==).

Error layering: Repo wraps infra errors with context. Service translates to domain sentinels (ErrUserNotFound, ErrInsufficientFunds). Handler maps sentinels to HTTP/gRPC codes. Log errors only at system boundaries (handlers, consumers, workers), not at every layer.

// Domain sentinels
var ErrUserNotFound = errors.New("user not found")

// Service: translate infra → domain
if errors.Is(err, sql.ErrNoRows) { return nil, ErrUserNotFound }

// Handler: map domain → HTTP
if errors.Is(err, ErrUserNotFound) { http.Error(w, "not found", 404); return }

Structured errors (APIs only): For HTTP/gRPC APIs needing error codes in responses:

type AppError struct {
    Code    string // "USER_NOT_FOUND", machine-readable
    Message string // Human-readable
    Err     error  // Wrapped cause
}
func (e *AppError) Error() string { return fmt.Sprintf("%s: %s", e.Code, e.Message) }
func (e *AppError) Unwrap() error { return e.Err }

Not needed for CLIs, workers, or internal packages: use sentinels + %w wrapping.

Testing: Table-driven with t.Run(), use t.Helper() in helpers.

tests := []struct{ name string; a, b, want int }{
    {"positive", 2, 3, 5},
}
for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
        if got := Add(tt.a, tt.b); got != tt.want {
            t.Errorf("Add(%d,%d)=%d; want %d", tt.a, tt.b, got, tt.want)
        }
    })
}

Build tags for simulation: //go:build simulation in driver_sim.go, //go:build !simulation in driver_real.go. Same type, different impl. Use for hardware, external APIs, infra deps.


Architecture & Design

Project structure:

cmd/api-server/main.go    # Entry points
internal/domain/          # Business entities
internal/service/         # Use cases
internal/repository/      # Data access

Organize by feature/domain, not technical layer. Avoid /src, /utils, /common, /helpers.

Functional Options:

type Option func(*Server)
func WithPort(p int) Option { return func(s *Server) { s.port = p } }
func NewServer(opts ...Option) *Server { /* apply opts */ }

Constructor Injection: Accept interfaces, return structs. No global mutable state: pass deps explicitly.

Interfaces: Small (1-3 methods), accept interfaces, return structs.

Useful Zero Values: Uninitialized struct = safe to use or obviously invalid. Stdlib examples: sync.Mutex, bytes.Buffer.


Concurrency

Golden Rules:

  • Always know WHEN and HOW a goroutine terminates
  • Libraries are synchronous: never launch goroutines from lib code unless concurrency IS the feature

errgroup (preferred over WaitGroup), context always first param, bounded pools for load, sender closes channels. Pre-1.23: time.After in loops leaks timers, use time.NewTicker. 1.23+: time.Tick is GC-safe (requires go 1.23 in go.mod).

For detailed concurrency patterns, performance optimization, profiling, and code review checklists, see references/golang-patterns.md.


Resources

Effective Go | Code Review Comments | Release Notes | goperf.dev | fgprof

Weekly Installs
12
GitHub Stars
8
First Seen
Mar 1, 2026
Installed on
cline12
github-copilot12
codex12
kimi-cli12
gemini-cli12
cursor12