--- name: golang version: 2.0.0 description: Use when building Go backend services, implementing goroutines/channels, handling errors idiomatically, writing tests with testify, or following Go best practices for APIs/CLI tools. keywords: - Go - Golang - concurrency - goroutines - channels - error handling - testing - backend - API plugin: dev updated: 2026-01-20 --- # Go Development Best Practices **Version**: 2.0.0 **Purpose**: Comprehensive Go development patterns covering idioms, error handling, concurrency, testing, and quality **Scope**: Backend development with Go - API services, CLI tools, system software **Prerequisites**: Basic Go syntax knowledge ## Overview Go (Golang) is designed for simplicity, explicit error handling, and safe concurrent programming. This skill covers production-ready patterns validated by the Go community, official documentation, and industry standards (Uber Engineering, Google). **Core Philosophy**: - **Simplicity**: "Clear is better than clever" - favor readable code over abstractions - **Explicit over implicit**: No exceptions, no hidden control flow, visible errors - **Composition over inheritance**: Interfaces and embedding, not class hierarchies - **Built-in concurrency**: Goroutines and channels as first-class primitives - **Tooling-first**: Format, vet, test, and benchmark built into the language **Key Design Principles**: 1. Small interfaces (1-3 methods ideal) 2. Consumer-side interface placement 3. Error values, not exceptions 4. Happy path at left margin 5. Goroutines must have explicit termination --- ## 1. Idiomatic Go Patterns ### 1.1 Naming Conventions **Package Names**: ```go // ✅ GOOD: Package names are single lowercase identifiers // Import path: "net/url" → package name: url // Import path: "encoding/json" → package name: json package url // from "net/url" package json // from "encoding/json" package strings // ❌ BAD package urls // No plural package encodingjson // Don't smash words together package stringutils // Too verbose ``` **Getters and Setters**: ```go type Account struct { balance int } // ✅ GOOD: No "Get" prefix func (a *Account) Balance() int { return a.balance } func (a *Account) SetBalance(amount int) { a.balance = amount } // ❌ BAD: Java-style getters func (a *Account) GetBalance() int { return a.balance } ``` **Error Variables**: ```go // Exported sentinel errors (capitalized) var ErrNotFound = errors.New("not found") var ErrTimeout = errors.New("timeout") // Unexported internal errors (lowercase) var errInternal = errors.New("internal error") ``` **Interface Naming**: ```go // ✅ GOOD: Short, descriptive type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } // ❌ BAD: Verbose or unclear type DataReader interface { ... } type IReader interface { ... } // No "I" prefix ``` --- ### 1.2 Interface Design - "The Bigger the Interface, the Weaker the Abstraction" **Core Principle**: Small, consumer-side interfaces provide maximum flexibility. **Single-Method Interfaces** (Ideal): ```go // Standard library examples type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } // Compose interfaces type ReadCloser interface { Reader Closer } ``` **Consumer-Side Interface Placement**: ```go // ❌ WRONG: Producer defines interface package store type CustomerStorage interface { StoreCustomer(Customer) error GetCustomer(string) (Customer, error) UpdateCustomer(Customer) error // 10+ methods... } type PostgresStore struct {} func (s *PostgresStore) StoreCustomer(...) { ... } // ✅ CORRECT: Consumer defines what it needs package client type customerGetter interface { GetCustomer(string) (store.Customer, error) } func ProcessCustomer(cg customerGetter) { customer, _ := cg.GetCustomer("123") // Only depends on GetCustomer method } ``` **Return Concrete Types, Accept Interfaces** (Postel's Law): ```go // ✅ GOOD func NewStore() *PostgresStore { return &PostgresStore{} } func Process(storage CustomerStorage) error { // Accepts interface } // ❌ BAD: Returning interface func NewStore() CustomerStorage { return &PostgresStore{} } ``` **When to Create Interfaces**: - Multiple implementations exist or are planned - Need for testing (mocking dependencies) - Decoupling packages - **NOT for**: Single implementation with no testing need --- ### 1.3 Happy Path Left, Early Returns **Core Principle**: Align success path to left margin, handle errors first. ```go // ❌ BAD: Deep nesting func join(s1, s2 string, max int) (string, error) { if s1 == "" { return "", errors.New("s1 is empty") } else { if s2 == "" { return "", errors.New("s2 is empty") } else { concat, err := concatenate(s1, s2) if err != nil { return "", err } else { if len(concat) > max { return concat[:max], nil } else { return concat, nil } } } } } // ✅ GOOD: Happy path aligned left func join(s1, s2 string, max int) (string, error) { if s1 == "" { return "", errors.New("s1 is empty") } if s2 == "" { return "", errors.New("s2 is empty") } concat, err := concatenate(s1, s2) if err != nil { return "", err } if len(concat) > max { return concat[:max], nil } return concat, nil } ``` **Guidelines**: - Maximum 3-4 levels of nesting - Omit `else` blocks when `if` returns - Handle errors immediately - Keep normal flow at lowest indentation --- ### 1.4 Composition Over Inheritance **Type Embedding** (Struct Composition): ```go // Embedding for method promotion type Logger struct { *log.Logger prefix string } func NewLogger(prefix string) *Logger { return &Logger{ Logger: log.New(os.Stdout, "", 0), prefix: prefix, } } // Logger methods automatically available logger := NewLogger("APP") logger.Println("message") // Calls embedded log.Logger.Println ``` **Interface Composition**: ```go type Reader interface { Read(p []byte) (n int, err error) } type Closer interface { Close() error } // Compose interfaces type ReadCloser interface { Reader Closer } ``` **Warning**: Avoid embedding in public APIs: ```go // ❌ BAD: Exposes implementation details type MyHandler struct { http.Handler // Leaks all Handler methods } // ✅ GOOD: Explicit delegation type MyHandler struct { handler http.Handler } func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Custom logic h.handler.ServeHTTP(w, r) } ``` --- ### 1.5 Key Go Idioms **Defer for Cleanup**: ```go func processFile(path string) error { f, err := os.Open(path) if err != nil { return err } defer f.Close() // Guaranteed cleanup // Multiple returns, all close file if condition { return nil // File closed } return process(f) // File closed } // Mutex pattern func (c *Counter) Increment() { c.mu.Lock() defer c.mu.Unlock() c.value++ // All paths unlock } ``` **Critical Rule**: Call `defer` AFTER checking error: ```go // ❌ WRONG defer f.Close() // f is nil if Open failed f, err := os.Open(path) // ✅ CORRECT f, err := os.Open(path) if err != nil { return err } defer f.Close() ``` **Multiple Return Values**: ```go // (value, error) - Standard error handling func GetUser(id string) (*User, error) { // ... } // (value, bool) - "comma ok" idiom value, ok := myMap[key] if !ok { // key not found } result, ok := someValue.(TargetType) if !ok { // type assertion failed } data, ok := <-channel if !ok { // channel closed } ``` **Blank Identifier `_`**: ```go // Ignore unwanted values _, err := os.Open(filename) // Compile-time interface check var _ http.Handler = (*MyHandler)(nil) // Import for side effects import _ "net/http/pprof" ``` **Useful Zero Values**: ```go // sync.Mutex - ready to use var mu sync.Mutex mu.Lock() // Works immediately // bytes.Buffer - valid empty buffer var buf bytes.Buffer buf.WriteString("hello") // No initialization needed // Slices - safe to read var s []int fmt.Println(len(s)) // 0 (safe) ``` --- ## 2. Error Handling ### 2.1 Error Wrapping with %w (Go 1.13+) **Core Pattern**: Wrap errors with context using `fmt.Errorf` and `%w`. ```go func processFile(path string) error { file, err := os.Open(path) if err != nil { // Wrap with context using %w return fmt.Errorf("failed to open file %s: %w", path, err) } defer file.Close() data, err := io.ReadAll(file) if err != nil { return fmt.Errorf("failed to read file %s: %w", path, err) } return processData(data) } // Result when error bubbles up: // "failed to initialize: failed to open file config.json: open config.json: no such file or directory" ``` **Checking Wrapped Errors**: ```go // errors.Is - Check for specific error in chain if errors.Is(err, os.ErrNotExist) { fmt.Println("File doesn't exist") } // errors.As - Extract specific error type var pathErr *os.PathError if errors.As(err, &pathErr) { fmt.Printf("Path error on: %s\n", pathErr.Path) } ``` **Critical**: Use `%w`, NOT `%v`: ```go // ❌ WRONG: Breaks error chain return fmt.Errorf("failed: %v", err) // ✅ CORRECT: Preserves chain return fmt.Errorf("failed: %w", err) ``` --- ### 2.2 Sentinel Errors vs Custom Error Types **Sentinel Errors** (Package-Level Variables): ```go package db var ( ErrConnectionFailed = errors.New("database connection failed") ErrRecordNotFound = errors.New("record not found") ErrDuplicateKey = errors.New("duplicate key violation") ) func GetUser(id int) (*User, error) { // ... if notFound { return nil, ErrRecordNotFound } return user, nil } // Caller checks with errors.Is user, err := db.GetUser(123) if errors.Is(err, db.ErrRecordNotFound) { // Handle not found } ``` **Custom Error Types** (Rich Context): ```go type ValidationError struct { Field string Value interface{} Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("validation failed for field '%s': %s (value: %v)", e.Field, e.Message, e.Value) } func validateAge(age int) error { if age < 0 { return &ValidationError{ Field: "age", Value: age, Message: "must be non-negative", } } return nil } // Caller extracts rich information if err := validateAge(-5); err != nil { var valErr *ValidationError if errors.As(err, &valErr) { fmt.Printf("Field: %s, Value: %v\n", valErr.Field, valErr.Value) } } ``` **Decision Guide**: - **Sentinel errors**: Simple, global error conditions - **Custom types**: Errors needing structured data or methods **Important**: Use pointer receivers for error types: ```go // ✅ CORRECT: Pointer receiver func (e *ValidationError) Error() string { ... } // ❌ WRONG: Value receiver (breaks errors.As) func (e ValidationError) Error() string { ... } ``` --- ### 2.3 Handle Errors Once **Core Principle**: Either log the error OR return it, not both. ```go // ❌ BAD: Handle twice (log AND return) if err != nil { log.Printf("error: %v", err) // Logged here return err // And returned } // ✅ GOOD: Return error, let caller handle if err != nil { return fmt.Errorf("process: %w", err) } // ✅ GOOD: Log and handle completely if err != nil { log.Printf("non-fatal error: %v", err) // Continue execution (error handled) } ``` **Error Message Conventions**: ```go // ✅ GOOD var ErrNotFound = errors.New("configuration file not found") return fmt.Errorf("failed to read settings for user %d: %w", userID, err) // ❌ BAD var ErrNotFound = errors.New("Error: Configuration file not found.") // No prefix, no punctuation return fmt.Errorf("Error occurred: %v", err) // Too generic ``` --- ### 2.4 Panic vs Error Decision Tree ``` Is this condition expected during normal operation? ├─ Yes → Return error └─ No → Is this a programmer error? ├─ Yes → Panic (with clear message) └─ No → Is the program in an invalid state? ├─ Yes → Panic └─ No → Return error ``` **Use Errors When**: ```go // Expected failures func readConfig(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read config: %w", err) } return parseConfig(data) } // Business logic failures func createUser(email string) error { if !isValidEmail(email) { return fmt.Errorf("invalid email format: %s", email) } return nil } ``` **Use Panic When**: ```go // Nil argument (programmer error, document this!) func ProcessData(data *Data) { if data == nil { panic("ProcessData: data argument must not be nil") } // ... } // Initialization failure func init() { cfg, err := loadConfig() if err != nil { panic(fmt.Sprintf("fatal: failed to load config: %v", err)) } globalConfig = cfg } // Impossible condition (indicates bug) func (sm *StateMachine) transition(event Event) { newState := sm.computeNextState(event) if !sm.isValidTransition(newState) { panic(fmt.Sprintf("BUG: invalid state transition from %v to %v", sm.currentState, newState)) } sm.currentState = newState } ``` **Recovery** (Use Sparingly): ```go // HTTP server recovering from handler panics func safeHandler(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { defer func() { if rec := recover(); rec != nil { log.Printf("Handler panic: %v\n%s", rec, debug.Stack()) http.Error(w, "Internal Server Error", 500) } }() h(w, r) } } ``` --- ### 2.5 Concurrent Error Handling **Pattern 1: errgroup (Coordinated Goroutines)**: ```go import "golang.org/x/sync/errgroup" func processFiles(ctx context.Context, files []string) error { g, ctx := errgroup.WithContext(ctx) for _, file := range files { file := file // Capture loop variable g.Go(func() error { return processFile(ctx, file) }) } // Wait for all, return first error if err := g.Wait(); err != nil { return fmt.Errorf("file processing failed: %w", err) } return nil } ``` **Pattern 2: Error Channel (Collect All Errors)**: ```go func processAll(items []Item) []error { errChan := make(chan error, len(items)) var wg sync.WaitGroup for _, item := range items { wg.Add(1) go func(i Item) { defer wg.Done() if err := process(i); err != nil { errChan <- err } }(item) } go func() { wg.Wait() close(errChan) }() var errs []error for err := range errChan { errs = append(errs, err) } return errs } ``` --- ## 3. Concurrency Patterns ### 3.1 Goroutine Lifecycle and Leak Prevention **Core Principle**: Every goroutine must have an explicit termination mechanism. **Pattern: Context Cancellation + WaitGroup**: ```go func runWorkers(ctx context.Context, n int) { var wg sync.WaitGroup for i := 0; i < n; i++ { wg.Add(1) go func(id int) { defer wg.Done() for { select { case <-ctx.Done(): return // Clean exit default: doWork(id) } } }(i) } wg.Wait() // Wait for all goroutines } // Usage ctx, cancel := context.WithCancel(context.Background()) go runWorkers(ctx, 10) // Later: stop all workers cancel() ``` **Common Leak: Unbuffered Channel Send**: ```go // ❌ LEAK: Goroutine blocks forever if no receiver func leak() { ch := make(chan int) go func() { ch <- 42 // Blocks forever }() // Function returns, goroutine leaked } // ✅ FIX: Buffered channel or ensure receiver func fixed() { ch := make(chan int, 1) // Buffer size 1 go func() { ch <- 42 // Won't block }() } ``` --- ### 3.2 Channel Patterns **Unbuffered vs Buffered Semantics**: ```go // Unbuffered: Synchronous handoff done := make(chan bool) go func() { doWork() done <- true // Blocks until main receives }() <-done // Guaranteed: work completed // Buffered: Asynchronous jobs := make(chan Job, 100) for w := 0; w < numWorkers; w++ { go func() { for job := range jobs { process(job) } }() } ``` **Channel Closing Rules**: ```go // ✅ GOOD: Only sender closes jobs := make(chan Job) go func() { for _, job := range allJobs { jobs <- job } close(jobs) // Signal: no more jobs }() for job := range jobs { process(job) // Exits when channel closed } // ❌ NEVER: Close from receiver // ❌ NEVER: Close closed channel (panics) // ❌ NEVER: Send on closed channel (panics) ``` **Select Pattern for Cancellation**: ```go func worker(ctx context.Context, jobs <-chan Job) { for { select { case job := <-jobs: process(job) case <-ctx.Done(): return // Cancel signal } } } ``` --- ### 3.3 Context Package for Cancellation **Context Types**: ```go // Root contexts ctx := context.Background() // Main/init ctx := context.TODO() // Placeholder // Cancellation ctx, cancel := context.WithCancel(parent) defer cancel() // Always call // Timeout ctx, cancel := context.WithTimeout(parent, 5*time.Second) defer cancel() // Deadline ctx, cancel := context.WithDeadline(parent, time.Now().Add(5*time.Second)) defer cancel() // Values (use sparingly, only for request-scoped data) ctx = context.WithValue(parent, key, value) ``` **Best Practices**: ```go // ✅ GOOD: Context as first parameter func makeRequest(ctx context.Context, url string) error { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) resp, err := client.Do(req) if err != nil { return err // Returns context.DeadlineExceeded on timeout } defer resp.Body.Close() return nil } // Check cancellation in loops func longRunning(ctx context.Context) error { for { select { case <-ctx.Done(): return ctx.Err() default: processChunk() } } } ``` **Context Rules**: 1. Pass context as first parameter: `func Do(ctx context.Context, ...)` 2. Never store context in struct 3. Always call cancel function (prevents leak) 4. Use `WithValue` only for request-scoped data, not options --- ### 3.4 Sync Primitives **sync.Mutex**: ```go type Counter struct { mu sync.Mutex value int } func (c *Counter) Increment() { c.mu.Lock() defer c.mu.Unlock() c.value++ } ``` **sync.RWMutex** (Read-Heavy Workloads): ```go type Cache struct { mu sync.RWMutex items map[string]Item } func (c *Cache) Get(key string) (Item, bool) { c.mu.RLock() // Multiple readers allowed defer c.mu.RUnlock() item, ok := c.items[key] return item, ok } func (c *Cache) Set(key string, item Item) { c.mu.Lock() // Exclusive write defer c.mu.Unlock() c.items[key] = item } ``` **sync.WaitGroup**: ```go var wg sync.WaitGroup for _, item := range items { wg.Add(1) // BEFORE starting goroutine go func(i Item) { defer wg.Done() process(i) }(item) } wg.Wait() // Block until all complete ``` **sync.Once** (One-Time Initialization): ```go var ( instance *Singleton once sync.Once ) func GetInstance() *Singleton { once.Do(func() { instance = &Singleton{} instance.init() }) return instance } ``` **sync/atomic** (Lock-Free): ```go type Counter struct { value atomic.Int64 // Go 1.19+ } func (c *Counter) Increment() int64 { return c.value.Add(1) } ``` **When to Use What**: - **Mutex**: Protecting compound operations, complex state - **RWMutex**: Read-heavy (10:1 read:write ratio+) - **WaitGroup**: Waiting for goroutines - **Once**: Lazy initialization - **Atomic**: Simple counters, flags - **Channels**: Communication, coordination --- ### 3.5 Worker Pool Pattern ```go func workerPool(ctx context.Context, numWorkers int, jobs <-chan Job, results chan<- Result) { var wg sync.WaitGroup for w := 0; w < numWorkers; w++ { wg.Add(1) go func(id int) { defer wg.Done() for { select { case job, ok := <-jobs: if !ok { return // Jobs channel closed } result := processJob(job) select { case results <- result: case <-ctx.Done(): return } case <-ctx.Done(): return } } }(w) } wg.Wait() close(results) // Signal completion } // Usage ctx, cancel := context.WithCancel(context.Background()) defer cancel() jobs := make(chan Job, 100) results := make(chan Result, 100) go workerPool(ctx, 10, jobs, results) // Send jobs go func() { for _, job := range allJobs { jobs <- job } close(jobs) }() // Collect results for result := range results { handleResult(result) } ``` --- ### 3.6 Race Detection **Running Race Detector**: ```bash go test -race ./... go build -race go run -race main.go ``` **Common Race Conditions**: ```go // ❌ RACE: Unsynchronized map var cache = make(map[string]string) func get(key string) string { return cache[key] // RACE } func set(key, value string) { cache[key] = value // RACE } // ✅ FIX: Use sync.Map var cache sync.Map func get(key string) string { val, _ := cache.Load(key) return val.(string) } // ❌ RACE: Loop variable capture for _, item := range items { go func() { process(item) // RACE }() } // ✅ FIX: Pass as parameter for _, item := range items { go func(i Item) { process(i) }(item) } ``` --- ## 4. Testing Patterns ### 4.1 Table-Driven Tests **Standard Pattern**: ```go func TestAdd(t *testing.T) { tests := []struct { name string a, b int expected int }{ {"positive numbers", 2, 3, 5}, {"negative numbers", -2, -3, -5}, {"mixed signs", -2, 3, 1}, {"zeros", 0, 0, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := Add(tt.a, tt.b) if result != tt.expected { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected) } }) } } ``` **Best Practices**: - Always use `t.Run()` for subtests - Descriptive test case names - Use anonymous structs for test data - Enable parallel execution with `t.Parallel()` --- ### 4.2 Test Helpers with t.Helper() ```go func assertEqual(t *testing.T, got, want interface{}) { t.Helper() // Error points to caller, not here if got != want { t.Errorf("got %v, want %v", got, want) } } func setupTestDB(t *testing.T) *sql.DB { t.Helper() db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatalf("failed to open test db: %v", err) } t.Cleanup(func() { db.Close() // Automatic cleanup }) return db } ``` --- ### 4.3 Integration vs Unit Testing **Unit Test** (Fast, Isolated): ```go func TestCalculatePrice(t *testing.T) { t.Parallel() tests := []struct { name string quantity int price float64 expected float64 }{ {"single item", 1, 10.0, 10.0}, {"multiple items", 5, 10.0, 50.0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := CalculatePrice(tt.quantity, tt.price) if result != tt.expected { t.Errorf("got %v, want %v", result, tt.expected) } }) } } ``` **Integration Test** (Build Tag): ```go //go:build integration // +build integration package myapp_test func TestDatabaseOperations(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } db := setupTestDatabase(t) defer db.Close() err := InsertUser(db, &User{Name: "John"}) if err != nil { t.Fatalf("failed to insert user: %v", err) } } ``` **Running Tests**: ```bash go test ./... # Unit tests only go test -short ./... # Skip slow tests go test -tags=integration ./... # Integration tests ``` --- ## 5. Quality Checks ### 5.1 golangci-lint Configuration **Recommended .golangci.yml**: ```yaml run: timeout: 5m linters: enable: - errcheck # Unchecked errors - gosimple # Simplify code - govet # Go vet - staticcheck # Static analysis - unused # Unused code - gofmt # Formatting - goimports # Imports - revive # Fast linter - gosec # Security - errorlint # Error wrapping linters-settings: errcheck: check-type-assertions: true check-blank: true govet: enable-all: true revive: rules: - name: error-strings - name: error-naming - name: exported - name: indent-error-flow issues: exclude-rules: - path: _test\.go linters: - errcheck - gosec ``` --- ### 5.2 Running Quality Checks **Standard Workflow**: ```bash # Format Go code go fmt ./... # Static analysis go vet ./... # Comprehensive linting (if golangci-lint installed) golangci-lint run # Run tests with race detector go test -race ./... # Coverage go test -cover ./... go test -coverprofile=coverage.out ./... go tool cover -html=coverage.out ``` **CI/CD Integration (GitHub Actions)**: ```yaml - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: version: latest - name: Tests run: go test -race -coverprofile=coverage.out ./... - name: Coverage run: | COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}') if (( $(echo "$COVERAGE < 80" | bc -l) )); then exit 1 fi ``` --- ## 6. Structured Logging Patterns ### 6.1 Structured Logging with slog (Go 1.21+) **Basic Usage**: ```go import "log/slog" func main() { // JSON handler for production logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) logger.Info("server starting", slog.String("port", "8080"), slog.Int("workers", 10)) // With context logger.InfoContext(ctx, "request processed", slog.String("method", "GET"), slog.String("path", "/api/users"), slog.Duration("latency", 45*time.Millisecond)) } ``` **Log Levels**: ```go logger.Debug("debug message") // Development logger.Info("info message") // General info logger.Warn("warning message") // Warnings logger.Error("error message") // Errors ``` **Request Context Logging**: ```go func requestLogger(ctx context.Context, logger *slog.Logger) *slog.Logger { requestID := ctx.Value("request_id").(string) return logger.With( slog.String("request_id", requestID), slog.String("user_id", getUserID(ctx)), ) } // Usage in handler func handleRequest(w http.ResponseWriter, r *http.Request) { log := requestLogger(r.Context(), baseLogger) log.Info("processing request", slog.String("path", r.URL.Path), slog.String("method", r.Method)) // All logs include request_id and user_id log.Error("database query failed", slog.String("error", err.Error())) } ``` --- ## 7. Anti-Patterns to Avoid ### Critical Anti-Patterns with Severity Tags **1. [CRITICAL] Swallowing Errors**: ```go // ❌ WRONG data, _ := fetchData() // ✅ CORRECT data, err := fetchData() if err != nil { return fmt.Errorf("fetch failed: %w", err) } ``` **2. [CRITICAL] Using %v Instead of %w**: ```go // ❌ WRONG: Breaks error chain return fmt.Errorf("failed: %v", err) // ✅ CORRECT return fmt.Errorf("failed: %w", err) ``` **3. [HIGH] Defer in Hot Loops**: ```go // ❌ WRONG: Defers accumulate for _, item := range items { mu.Lock() defer mu.Unlock() // Never executes until function returns process(item) } // ✅ CORRECT for _, item := range items { mu.Lock() process(item) mu.Unlock() } ``` **4. [CRITICAL] Goroutine Leaks**: ```go // ❌ WRONG: No way to stop go func() { for { doWork() } }() // ✅ CORRECT: Context cancellation go func() { for { select { case <-ctx.Done(): return default: doWork() } } }() ``` **5. [CRITICAL] Loop Variable Capture**: ```go // ❌ WRONG: All goroutines see last value for _, item := range items { go func() { process(item) // RACE }() } // ✅ CORRECT: Pass as parameter for _, item := range items { go func(i Item) { process(i) }(item) } ``` **6. [MEDIUM] Map Without Pre-allocation**: ```go // ❌ WRONG: Multiple rehashes m := make(map[string]Item) for _, item := range items { m[item.ID] = item } // ✅ CORRECT: Pre-sized m := make(map[string]Item, len(items)) ``` **7. [HIGH] time.After in Loops (Memory Leak)**: ```go // ❌ WRONG: Creates timer each iteration for { select { case <-time.After(5 * time.Second): timeout() } } // ✅ CORRECT: Reuse timer timer := time.NewTimer(5 * time.Second) defer timer.Stop() for { select { case <-timer.C: timeout() timer.Reset(5 * time.Second) } } ``` **8. [HIGH] Checking Error Strings**: ```go // ❌ WRONG: Fragile if err != nil && strings.Contains(err.Error(), "not found") { // ... } // ✅ CORRECT: Use errors.Is if errors.Is(err, sql.ErrNoRows) { // ... } ``` **9. [CRITICAL] Not Calling cancel()**: ```go // ❌ WRONG: Context leak ctx, cancel := context.WithCancel(parent) doWork(ctx) // ✅ CORRECT: Always defer cancel ctx, cancel := context.WithCancel(parent) defer cancel() doWork(ctx) ``` **10. [MEDIUM] Producer-Side Interfaces**: ```go // ❌ WRONG package store type Storage interface { ... } type Store struct {} // ✅ CORRECT package client type storage interface { ... } // Define where used ``` --- ## References **Official Documentation**: - [Effective Go](https://go.dev/doc/effective_go) - [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments) - [Go Blog: Error Handling](https://go.dev/blog/go1.13-errors) - [Go Blog: Context](https://go.dev/blog/context) **Industry Standards**: - [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md) - [100 Go Mistakes](https://www.manning.com/books/100-go-mistakes-and-how-to-avoid-them) **Research Source**: - Comprehensive research: `/ai-docs/sessions/dev-research-golang-best-practices-20260106-233135-773b0173/report.md` - 15 unanimous consensus patterns - 42 high-quality sources (100% official/industry standards) - Zero contradictions found --- **Skill Version**: 2.0.0 **Last Updated**: January 7, 2026 **Maintainer**: MadAppGang/claude-code