Claude Skill

go-ops

Go development patterns, concurrency, error handling, testing, and project structure. Use for: golang, go, goroutine, channel, context, errgroup, go test, go mod, go build, interface, generics, table-driven tests, worker pool, sync.Mutex, sync.WaitGroup, pprof, go vet, golangci-l

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download 0xdarkmatter-claude-mods-skills_go-ops-3dfaf0b.zip · 40 KB
Part of 0xdarkmatter/claude-mods — 94 skills

Install

skills CLI npx skills add https://github.com/0xDarkMatter/claude-mods/tree/main/skills/go-ops
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install 0xdarkmatter-claude-mods@llmmart
Git git clone https://github.com/0xDarkMatter/claude-mods.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole 0xdarkmatter/claude-mods collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Go Operations

Comprehensive Go skill covering idiomatic patterns, concurrency, and production practices.

Module Quick Start

# New module
go mod init github.com/user/project

# Add dependency
go get github.com/lib/pq@latest

# Tidy (remove unused, add missing)
go mod tidy

# Vendor dependencies
go mod vendor

# Workspace (multi-module)
go work init ./api ./shared
go work use ./cli

Error Handling Decision Tree

What kind of error?
│
├─ Known, expected condition (e.g. "not found")
│  └─ Sentinel error: var ErrNotFound = errors.New("not found")
│     └─ Caller checks: errors.Is(err, ErrNotFound)
│
├─ Need to carry structured data (status code, field name)
│  └─ Custom error type: type ValidationError struct { Field, Message string }
│     └─ Implement Error() string
│     └─ Caller checks: errors.As(err, &validErr)
│
├─ Adding context to an existing error
│  └─ Wrap: fmt.Errorf("load config: %w", err)
│     └─ Preserves original for Is/As checks
│
├─ Truly unrecoverable (corrupted state, programmer bug)
│  └─ panic("invariant violated: ...")
│     └─ Almost never in library code
│
└─ Multiple errors from concurrent work
   └─ errors.Join(err1, err2) or multierr package

Error Wrapping Convention

// Add context at each layer, don't repeat the function name
func LoadUser(id int) (*User, error) {
    row, err := db.Query("SELECT ...", id)
    if err != nil {
        return nil, fmt.Errorf("load user %d: %w", id, err)
    }
    // ...
}

Concurrency Decision Tree

What's the concurrency pattern?
│
├─ Run N independent tasks, collect results
│  └─ errgroup.Group (cancels on first error)
│
├─ Fire-and-forget background work
│  └─ go func() with context for cancellation
│     └─ ALWAYS handle the error or log it
│
├─ Producer/consumer pipeline
│  └─ Channels (buffered for throughput)
│     └─ Close channel when producer is done
│
├─ Rate-limited concurrent work
│  └─ Semaphore: make(chan struct{}, maxConcurrency)
│
├─ Shared mutable state
│  └─ sync.Mutex or sync.RWMutex
│     └─ Prefer channels if the state is simple
│
├─ One-time initialization
│  └─ sync.Once
│
└─ Wait for N goroutines to finish (no error collection)
   └─ sync.WaitGroup

errgroup Quick Start

import "golang.org/x/sync/errgroup"

g, ctx := errgroup.WithContext(ctx)
g.SetLimit(10) // max 10 concurrent goroutines

for _, url := range urls {
    g.Go(func() error {
        return fetch(ctx, url)
    })
}

if err := g.Wait(); err != nil {
    return fmt.Errorf("fetch urls: %w", err)
}

Deep dive: Load ./references/concurrency.md for worker pools, fan-out/fan-in, pipeline patterns, context best practices.

Interface Design

Accept interfaces, return structs.
// Good: function accepts interface
func Process(r io.Reader) error { ... }

// Good: return concrete type
func NewServer(cfg Config) *Server { ... }

// Bad: returning interface (hides implementation, prevents extension)
func NewServer(cfg Config) ServerInterface { ... }

Common Stdlib Interfaces

Interface Methods Use For
io.Reader Read([]byte) (int, error) Any byte source
io.Writer Write([]byte) (int, error) Any byte sink
io.Closer Close() error Resource cleanup
fmt.Stringer String() string String representation
error Error() string Error values
sort.Interface Len, Less, Swap Custom sorting
http.Handler ServeHTTP(w, r) HTTP handlers
encoding.BinaryMarshaler MarshalBinary() ([]byte, error) Binary encoding

Functional Options Pattern

type Option func(*Server)

func WithPort(port int) Option {
    return func(s *Server) { s.port = port }
}

func WithTimeout(d time.Duration) Option {
    return func(s *Server) { s.timeout = d }
}

func NewServer(opts ...Option) *Server {
    s := &Server{port: 8080, timeout: 30 * time.Second} // defaults
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Usage
srv := NewServer(WithPort(9090), WithTimeout(5*time.Second))

Deep dive: Load ./references/interfaces-generics.md for generics, type constraints, embedding, type assertions.

Testing Quick Reference

// Table-driven test
func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 1, 2, 3},
        {"zero", 0, 0, 0},
        {"negative", -1, -2, -3},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.expected {
                t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.expected)
            }
        })
    }
}
# Run tests
go test ./...

# With coverage
go test -cover -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

# Run specific test
go test -run TestAdd ./pkg/math/

# Benchmarks
go test -bench=. -benchmem ./...

# Race detector
go test -race ./...

# Fuzz testing
go test -fuzz=FuzzParse ./...

Deep dive: Load ./references/testing.md for mocking with interfaces, httptest, testcontainers, golden files.

Common Gotchas

Gotcha Why Fix
Nil slice vs empty slice var s []int is nil, s := []int{} is empty. json.Marshal gives null vs [] Use make([]int, 0) or []int{} if JSON matters
Goroutine leak Goroutine blocked on channel with no reader/writer Use context.WithCancel, always provide exit path
Defer in loop Deferred calls don't run until function returns Wrap loop body in a closure or use explicit cleanup
Interface nil pitfall (*MyType)(nil) assigned to error interface is not == nil Return nil explicitly, not a nil typed pointer
Range variable capture Loop var reused (pre-Go 1.22) Use go func(v T) { ... }(v) or upgrade to Go 1.22+
String concatenation in loop O(n^2) allocation Use strings.Builder
sync.WaitGroup Add after Go Race condition Call wg.Add(1) before go func()
Unbuffered channel deadlock Send/receive must happen concurrently Use buffered channel or separate goroutines
map not safe for concurrent use Race condition, may crash Use sync.Mutex or sync.Map

Project Structure

project/
├── cmd/
│   ├── api/main.go           # Entry points
│   └── worker/main.go
├── internal/                  # Private packages
│   ├── handler/
│   ├── service/
│   └── repository/
├── pkg/                       # Public packages (optional)
├── go.mod
├── go.sum
├── Makefile                   # or justfile
└── .golangci.yml

Deep dive: Load ./references/project-structure.md for workspace mode, build tags, ldflags, linting config.

Performance Quick Reference

# CPU profile
go test -cpuprofile=cpu.prof -bench=. ./...
go tool pprof cpu.prof

# Memory profile
go test -memprofile=mem.prof -bench=. ./...
go tool pprof -alloc_space mem.prof

# Trace
go test -trace=trace.out ./...
go tool trace trace.out

# Escape analysis
go build -gcflags='-m' ./...
Optimization When Pattern
Pre-allocate slices Known size make([]T, 0, n)
strings.Builder String concatenation var b strings.Builder
sync.Pool Frequent alloc/free of same type pool.Get() / pool.Put()
Struct field alignment Memory-sensitive Group fields by size (largest first)
Buffer reuse I/O-heavy bufio.NewReaderSize(r, 64*1024)

Deep dive: Load ./references/performance.md for pprof walkthrough, benchmarking patterns, escape analysis.

Reference Files

Load these for deep-dive topics. Each is self-contained.

Reference When to Load
./references/concurrency.md Goroutines, channels, context, sync primitives, worker pools, pipelines
./references/error-handling.md Error wrapping, sentinel errors, custom types, multi-error, panic/recover
./references/testing.md Table tests, mocking, httptest, benchmarks, fuzz, testcontainers, golden files
./references/interfaces-generics.md Interface design, embedding, type assertions, generics, type constraints
./references/project-structure.md Standard layout, go.mod, workspaces, build tags, ldflags, golangci-lint
./references/performance.md pprof, trace, benchmarks, escape analysis, sync.Pool, struct alignment
./references/expert-insights.md HTTP server (Go 1.22 routing), graceful shutdown, http.Client tuning, JSON helpers

See Also

  • docker-ops - Multi-stage builds for Go binaries (scratch/distroless)
  • ci-cd-ops - Go CI pipelines, caching go modules, goreleaser
  • testing-ops - Cross-language testing strategies
Files (claude-mods)
  • assets
    • .gitkeep 0 B · in bundle
  • references
    • concurrency.md 18.8 KB
      # Go Concurrency Reference
      
      ## Table of Contents
      
      1. [Goroutines](#goroutines)
      2. [Channels](#channels)
      3. [Select](#select)
      4. [Context](#context)
      5. [Sync Primitives](#sync-primitives)
      6. [errgroup](#errgroup)
      7. [Worker Pool Pattern](#worker-pool-pattern)
      8. [Fan-out / Fan-in](#fan-out--fan-in)
      9. [Pipeline Pattern](#pipeline-pattern)
      10. [Rate Limiting](#rate-limiting)
      11. [Common Mistakes](#common-mistakes)
      
      ---
      
      ## Goroutines
      
      ### Launch Patterns
      
      ```go
      // Anonymous function - capture variables carefully
      go func() {
          doWork()
      }()
      
      // Named function
      go processItem(item)
      
      // Method
      go srv.handleConnection(conn)
      
      // Capture loop variable correctly (pre-Go 1.22)
      for _, item := range items {
          item := item // shadow to capture
          go func() {
              process(item)
          }()
      }
      
      // Go 1.22+: loop variable captured per iteration automatically
      for _, item := range items {
          go func() {
              process(item) // safe in Go 1.22+
          }()
      }
      ```
      
      ### Goroutine Lifecycle
      
      Every goroutine needs an exit path. Establish ownership at creation time.
      
      ```go
      func startWorker(ctx context.Context, jobs <-chan Job) {
          go func() {
              for {
                  select {
                  case <-ctx.Done():
                      return // clean exit on cancellation
                  case job, ok := <-jobs:
                      if !ok {
                          return // channel closed
                      }
                      process(job)
                  }
              }
          }()
      }
      ```
      
      ### Avoid Goroutine Leaks
      
      A goroutine leaks when it blocks forever with no exit condition.
      
      ```go
      // LEAK: goroutine blocks on send forever if nobody reads
      func bad() {
          ch := make(chan int)
          go func() {
              ch <- compute() // blocks if caller exits
          }()
          // if caller returns without reading ch, goroutine leaks
      }
      
      // FIX: use buffered channel or context
      func good(ctx context.Context) {
          ch := make(chan int, 1) // buffer absorbs the send
          go func() {
              select {
              case ch <- compute():
              case <-ctx.Done():
              }
          }()
      }
      ```
      
      ### Cost Model
      
      - Initial stack: ~2 KB (grows as needed, up to 1 GB by default)
      - Goroutines are multiplexed onto OS threads by the Go scheduler
      - Switching between goroutines is cheap (~100 ns) vs OS thread switch (~1 µs)
      - Practical limit: tens of thousands of goroutines; millions is unusual but possible
      - Use `runtime.NumGoroutine()` to inspect count; expose via `pprof` in production
      
      ---
      
      ## Channels
      
      ### Buffered vs Unbuffered
      
      ```go
      // Unbuffered: sender blocks until receiver is ready (synchronous handoff)
      ch := make(chan int)
      
      // Buffered: sender blocks only when buffer is full
      ch := make(chan int, 10)
      ```
      
      Use unbuffered when you want a synchronization guarantee (the receiver got the value).
      Use buffered to decouple producer/consumer speeds or to avoid goroutine creation.
      
      ### Directional Channels
      
      ```go
      // Restrict channels at function boundaries for clarity and safety
      func produce(out chan<- int) { // send-only
          out <- 42
      }
      
      func consume(in <-chan int) { // receive-only
          v := <-in
          fmt.Println(v)
      }
      
      func wire() {
          ch := make(chan int, 1)
          go produce(ch)
          consume(ch)
      }
      ```
      
      ### Close Semantics
      
      ```go
      // Only the sender should close
      close(ch)
      
      // Closed channel returns zero value immediately
      v, ok := <-ch
      // ok == false means channel is closed and drained
      
      // Panic conditions:
      // - closing a nil channel
      // - closing an already-closed channel
      // - sending on a closed channel
      ```
      
      ### Range over Channel
      
      ```go
      // Range exits when channel is closed and drained
      for v := range ch {
          process(v)
      }
      
      // Equivalent explicit loop
      for {
          v, ok := <-ch
          if !ok {
              break
          }
          process(v)
      }
      ```
      
      ### Nil Channel Behavior
      
      ```go
      var ch chan int // nil channel
      
      // Sending or receiving on nil blocks forever
      // <-ch   // blocks
      // ch <- 1 // blocks
      
      // Useful in select to disable a case dynamically
      func merge(a, b <-chan int) <-chan int {
          out := make(chan int)
          go func() {
              defer close(out)
              for a != nil || b != nil {
                  select {
                  case v, ok := <-a:
                      if !ok {
                          a = nil // disable this case
                          continue
                      }
                      out <- v
                  case v, ok := <-b:
                      if !ok {
                          b = nil // disable this case
                          continue
                      }
                      out <- v
                  }
              }
          }()
          return out
      }
      ```
      
      ---
      
      ## Select
      
      ### Multi-channel Operations
      
      ```go
      select {
      case msg := <-ch1:
          handle(msg)
      case ch2 <- value:
          // sent successfully
      case <-done:
          return
      }
      ```
      
      ### Timeout Pattern
      
      ```go
      func fetchWithTimeout(url string, timeout time.Duration) (*Response, error) {
          result := make(chan *Response, 1)
          go func() {
              result <- fetch(url)
          }()
      
          select {
          case resp := <-result:
              return resp, nil
          case <-time.After(timeout):
              return nil, fmt.Errorf("fetch %s: timed out after %v", url, timeout)
          }
      }
      ```
      
      ### Non-blocking with Default
      
      ```go
      // Try to send/receive; skip if not ready
      select {
      case ch <- value:
          // sent
      default:
          // channel full or no receiver; drop or handle
      }
      
      // Non-blocking receive
      select {
      case v := <-ch:
          use(v)
      default:
          // nothing available right now
      }
      ```
      
      ### Priority Pattern
      
      Go's select is random when multiple cases are ready. Force priority explicitly.
      
      ```go
      // Drain high-priority channel before processing low-priority
      func prioritySelect(hi, lo <-chan Job) {
          for {
              select {
              case job := <-hi:
                  process(job)
              default:
                  // hi empty; check both
                  select {
                  case job := <-hi:
                      process(job)
                  case job := <-lo:
                      process(job)
                  }
              }
          }
      }
      ```
      
      ---
      
      ## Context
      
      ### Create Root Contexts
      
      ```go
      ctx := context.Background() // top-level; never cancelled
      ctx := context.TODO()       // placeholder; replace before shipping
      ```
      
      ### WithCancel
      
      ```go
      ctx, cancel := context.WithCancel(parent)
      defer cancel() // always defer to free resources
      
      go func() {
          <-ctx.Done()
          fmt.Println("cancelled:", ctx.Err()) // context.Canceled
      }()
      
      cancel() // trigger cancellation
      ```
      
      ### WithTimeout and WithDeadline
      
      ```go
      ctx, cancel := context.WithTimeout(parent, 5*time.Second)
      defer cancel()
      
      // WithDeadline takes an absolute time
      deadline := time.Now().Add(5 * time.Second)
      ctx, cancel = context.WithDeadline(parent, deadline)
      defer cancel()
      
      // Check remaining time
      if d, ok := ctx.Deadline(); ok {
          remaining := time.Until(d)
          fmt.Println("remaining:", remaining)
      }
      ```
      
      ### WithValue
      
      ```go
      type contextKey string // unexported to avoid collisions
      
      const requestIDKey contextKey = "request-id"
      
      func withRequestID(ctx context.Context, id string) context.Context {
          return context.WithValue(ctx, requestIDKey, id)
      }
      
      func requestIDFromContext(ctx context.Context) (string, bool) {
          id, ok := ctx.Value(requestIDKey).(string)
          return id, ok
      }
      ```
      
      ### Propagation Rules
      
      - Always pass `ctx` as the first argument to functions that do I/O
      - Never store context in a struct field (pass explicitly)
      - Derive child contexts; never modify the parent
      - Cancel is inherited: cancelling parent cancels all children
      
      ### HTTP Middleware Pattern
      
      ```go
      func requestIDMiddleware(next http.Handler) http.Handler {
          return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
              id := r.Header.Get("X-Request-ID")
              if id == "" {
                  id = generateID()
              }
              ctx := withRequestID(r.Context(), id)
              next.ServeHTTP(w, r.WithContext(ctx))
          })
      }
      
      func handler(w http.ResponseWriter, r *http.Request) {
          id, _ := requestIDFromContext(r.Context())
          // id flows through without explicit passing
      }
      ```
      
      ---
      
      ## Sync Primitives
      
      ### Mutex
      
      ```go
      type SafeMap struct {
          mu sync.Mutex
          m  map[string]int
      }
      
      func (s *SafeMap) Set(key string, val int) {
          s.mu.Lock()
          defer s.mu.Unlock()
          s.m[key] = val
      }
      
      func (s *SafeMap) Get(key string) (int, bool) {
          s.mu.Lock()
          defer s.mu.Unlock()
          v, ok := s.m[key]
          return v, ok
      }
      ```
      
      ### RWMutex
      
      Use when reads vastly outnumber writes.
      
      ```go
      type Cache struct {
          mu   sync.RWMutex
          data map[string]string
      }
      
      func (c *Cache) Get(key string) (string, bool) {
          c.mu.RLock()         // multiple readers allowed
          defer c.mu.RUnlock()
          v, ok := c.data[key]
          return v, ok
      }
      
      func (c *Cache) Set(key, val string) {
          c.mu.Lock()          // exclusive write
          defer c.mu.Unlock()
          c.data[key] = val
      }
      ```
      
      ### Once
      
      ```go
      var (
          instance *DB
          once     sync.Once
      )
      
      func GetDB() *DB {
          once.Do(func() {
              instance = connectDB()
          })
          return instance
      }
      ```
      
      ### WaitGroup
      
      ```go
      var wg sync.WaitGroup
      
      for _, url := range urls {
          wg.Add(1)
          go func(u string) {
              defer wg.Done()
              fetch(u)
          }(url)
      }
      
      wg.Wait() // block until all goroutines call Done
      ```
      
      ### Pool
      
      ```go
      var bufPool = sync.Pool{
          New: func() any {
              return new(bytes.Buffer)
          },
      }
      
      func processRequest(data []byte) []byte {
          buf := bufPool.Get().(*bytes.Buffer)
          defer func() {
              buf.Reset()
              bufPool.Put(buf)
          }()
      
          buf.Write(data)
          // transform buf...
          return buf.Bytes()
      }
      ```
      
      ### Atomic Operations
      
      ```go
      import "sync/atomic"
      
      var counter atomic.Int64
      
      counter.Add(1)
      counter.Store(0)
      v := counter.Load()
      swapped := counter.CompareAndSwap(old, new)
      
      // Prefer atomic for simple counters; prefer Mutex for compound operations
      ```
      
      ### When to Use Each
      
      | Primitive | Use When |
      |-----------|----------|
      | `Mutex` | Protecting a struct with multiple fields |
      | `RWMutex` | Read-heavy access; reads >> writes |
      | `Once` | One-time initialization |
      | `WaitGroup` | Waiting for a collection of goroutines |
      | `Pool` | Reusing temporary objects to reduce GC pressure |
      | `atomic` | Single integer/pointer with no compound operations |
      | Channel | Transferring ownership of data between goroutines |
      
      ---
      
      ## errgroup
      
      ### Basic Usage
      
      ```go
      import "golang.org/x/sync/errgroup"
      
      func fetchAll(ctx context.Context, urls []string) ([][]byte, error) {
          g, ctx := errgroup.WithContext(ctx)
          results := make([][]byte, len(urls))
      
          for i, url := range urls {
              i, url := i, url
              g.Go(func() error {
                  body, err := get(ctx, url)
                  if err != nil {
                      return fmt.Errorf("fetch %s: %w", url, err)
                  }
                  results[i] = body
                  return nil
              })
          }
      
          if err := g.Wait(); err != nil {
              return nil, err
          }
          return results, nil
      }
      ```
      
      ### Limit Concurrency with SetLimit
      
      ```go
      g, ctx := errgroup.WithContext(ctx)
      g.SetLimit(10) // at most 10 goroutines at a time
      
      for _, url := range urls {
          url := url
          g.Go(func() error {
              return process(ctx, url)
          })
      }
      
      return g.Wait()
      ```
      
      ### Collect Results Safely
      
      Pre-allocate the result slice before launching goroutines. Each goroutine writes to its own index — no mutex needed because slice indices do not overlap.
      
      ```go
      type Result struct {
          URL  string
          Data []byte
      }
      
      func gather(ctx context.Context, urls []string) ([]Result, error) {
          g, ctx := errgroup.WithContext(ctx)
          results := make([]Result, len(urls))
      
          for i, url := range urls {
              i, url := i, url
              g.Go(func() error {
                  data, err := get(ctx, url)
                  if err != nil {
                      return err
                  }
                  results[i] = Result{URL: url, Data: data}
                  return nil
              })
          }
      
          if err := g.Wait(); err != nil {
              return nil, err
          }
          return results, nil
      }
      ```
      
      ---
      
      ## Worker Pool Pattern
      
      ```go
      type Job struct {
          ID   int
          Data []byte
      }
      
      type Result struct {
          JobID  int
          Output []byte
          Err    error
      }
      
      func workerPool(
          ctx context.Context,
          jobs <-chan Job,
          numWorkers int,
      ) <-chan Result {
          results := make(chan Result, numWorkers)
      
          var wg sync.WaitGroup
          for i := 0; i < numWorkers; i++ {
              wg.Add(1)
              go func() {
                  defer wg.Done()
                  for {
                      select {
                      case <-ctx.Done():
                          return
                      case job, ok := <-jobs:
                          if !ok {
                              return
                          }
                          out, err := processJob(job)
                          results <- Result{JobID: job.ID, Output: out, Err: err}
                      }
                  }
              }()
          }
      
          // Close results when all workers finish
          go func() {
              wg.Wait()
              close(results)
          }()
      
          return results
      }
      
      func run(ctx context.Context, allJobs []Job) error {
          jobs := make(chan Job, len(allJobs))
          for _, j := range allJobs {
              jobs <- j
          }
          close(jobs)
      
          results := workerPool(ctx, jobs, 5)
      
          for r := range results {
              if r.Err != nil {
                  return fmt.Errorf("job %d: %w", r.JobID, r.Err)
              }
              fmt.Printf("job %d done\n", r.JobID)
          }
          return nil
      }
      ```
      
      ---
      
      ## Fan-out / Fan-in
      
      ### Fan-out: Distribute One Channel to Many Workers
      
      ```go
      func fanOut(in <-chan int, n int) []<-chan int {
          outs := make([]<-chan int, n)
          for i := 0; i < n; i++ {
              ch := make(chan int)
              outs[i] = ch
              go func() {
                  defer close(ch)
                  for v := range in {
                      ch <- v
                  }
              }()
          }
          return outs
      }
      ```
      
      ### Fan-in: Merge Multiple Channels into One
      
      ```go
      func fanIn(ctx context.Context, ins ...<-chan int) <-chan int {
          out := make(chan int)
          var wg sync.WaitGroup
      
          forward := func(ch <-chan int) {
              defer wg.Done()
              for {
                  select {
                  case v, ok := <-ch:
                      if !ok {
                          return
                      }
                      select {
                      case out <- v:
                      case <-ctx.Done():
                          return
                      }
                  case <-ctx.Done():
                      return
                  }
              }
          }
      
          wg.Add(len(ins))
          for _, ch := range ins {
              go forward(ch)
          }
      
          go func() {
              wg.Wait()
              close(out)
          }()
      
          return out
      }
      ```
      
      ---
      
      ## Pipeline Pattern
      
      Each stage reads from upstream and writes to downstream. Cancellation propagates via context.
      
      ```go
      func generate(ctx context.Context, nums ...int) <-chan int {
          out := make(chan int)
          go func() {
              defer close(out)
              for _, n := range nums {
                  select {
                  case out <- n:
                  case <-ctx.Done():
                      return
                  }
              }
          }()
          return out
      }
      
      func square(ctx context.Context, in <-chan int) <-chan int {
          out := make(chan int)
          go func() {
              defer close(out)
              for v := range in {
                  select {
                  case out <- v * v:
                  case <-ctx.Done():
                      return
                  }
              }
          }()
          return out
      }
      
      func filter(ctx context.Context, in <-chan int, pred func(int) bool) <-chan int {
          out := make(chan int)
          go func() {
              defer close(out)
              for v := range in {
                  if pred(v) {
                      select {
                      case out <- v:
                      case <-ctx.Done():
                          return
                      }
                  }
              }
          }()
          return out
      }
      
      func runPipeline(ctx context.Context) {
          nums := generate(ctx, 1, 2, 3, 4, 5)
          squares := square(ctx, nums)
          evens := filter(ctx, squares, func(n int) bool { return n%2 == 0 })
      
          for v := range evens {
              fmt.Println(v) // 4, 16
          }
      }
      ```
      
      ---
      
      ## Rate Limiting
      
      ### Semaphore Pattern
      
      ```go
      type Semaphore chan struct{}
      
      func NewSemaphore(n int) Semaphore {
          return make(Semaphore, n)
      }
      
      func (s Semaphore) Acquire() { s <- struct{}{} }
      func (s Semaphore) Release() { <-s }
      
      func fetchConcurrently(ctx context.Context, urls []string, limit int) {
          sem := NewSemaphore(limit)
          var wg sync.WaitGroup
      
          for _, url := range urls {
              url := url
              wg.Add(1)
              go func() {
                  defer wg.Done()
                  sem.Acquire()
                  defer sem.Release()
                  fetch(ctx, url)
              }()
          }
      
          wg.Wait()
      }
      ```
      
      ### time.Ticker Rate Limiter
      
      ```go
      func rateLimitedFetch(ctx context.Context, urls []string, rps int) error {
          ticker := time.NewTicker(time.Second / time.Duration(rps))
          defer ticker.Stop()
      
          for _, url := range urls {
              select {
              case <-ctx.Done():
                  return ctx.Err()
              case <-ticker.C:
                  if err := fetch(ctx, url); err != nil {
                      return err
                  }
              }
          }
          return nil
      }
      ```
      
      ### Token Bucket (using time/rate)
      
      ```go
      import "golang.org/x/time/rate"
      
      limiter := rate.NewLimiter(rate.Limit(100), 10) // 100 req/s, burst 10
      
      func callAPI(ctx context.Context, req Request) error {
          if err := limiter.Wait(ctx); err != nil {
              return fmt.Errorf("rate limiter: %w", err)
          }
          return sendRequest(req)
      }
      ```
      
      ---
      
      ## Common Mistakes
      
      ### Goroutine Leak: Blocking Send with No Receiver
      
      ```go
      // BAD
      func search(query string) <-chan Result {
          ch := make(chan Result) // unbuffered
          go func() {
              ch <- doSearch(query) // blocks if caller gives up
          }()
          return ch
      }
      
      // GOOD: buffer of 1 so goroutine never blocks
      func search(ctx context.Context, query string) <-chan Result {
          ch := make(chan Result, 1)
          go func() {
              select {
              case ch <- doSearch(ctx, query):
              case <-ctx.Done():
              }
          }()
          return ch
      }
      ```
      
      ### Race Condition: Shared Variable without Protection
      
      ```go
      // BAD: data race on count
      var count int
      var wg sync.WaitGroup
      for i := 0; i < 100; i++ {
          wg.Add(1)
          go func() {
              defer wg.Done()
              count++ // not safe
          }()
      }
      
      // GOOD: use atomic or mutex
      var count atomic.Int64
      for i := 0; i < 100; i++ {
          wg.Add(1)
          go func() {
              defer wg.Done()
              count.Add(1)
          }()
      }
      ```
      
      ### Deadlock: All Goroutines Waiting on Each Other
      
      ```go
      // BAD: both goroutines block trying to send before anyone reads
      ch := make(chan int)
      ch <- 1 // blocks main goroutine
      go func() { ch <- 2 }() // never reached
      
      // GOOD: buffer or launch reader first
      ch := make(chan int, 2)
      ch <- 1
      ch <- 2
      ```
      
      ### Closing a Channel from the Wrong Side
      
      ```go
      // BAD: receiver closes channel; sender may still write
      func consumer(ch chan int) {
          close(ch) // panics if sender writes after this
      }
      
      // GOOD: only the producer closes
      func producer(ch chan<- int) {
          defer close(ch)
          for _, v := range data {
              ch <- v
          }
      }
      ```
      
      ### WaitGroup Counter Mismatch
      
      ```go
      // BAD: Add inside goroutine; may call Wait before Add
      for _, item := range items {
          go func(item Item) {
              wg.Add(1) // too late
              defer wg.Done()
              process(item)
          }(item)
      }
      wg.Wait()
      
      // GOOD: Add before launching goroutine
      for _, item := range items {
          wg.Add(1)
          go func(item Item) {
              defer wg.Done()
              process(item)
          }(item)
      }
      wg.Wait()
      ```
      
      ### Detect Races at Test Time
      
      ```go
      // Always run tests with the race detector
      // go test -race ./...
      // go build -race ./cmd/server
      ```
      
    • error-handling.md 15.3 KB
      # Go Error Handling Reference
      
      ## Table of Contents
      
      1. [Error Basics](#error-basics)
      2. [Wrap Errors with %w](#wrap-errors-with-w)
      3. [errors.Is and errors.As](#errorsis-and-errorsas)
      4. [Sentinel Errors](#sentinel-errors)
      5. [Custom Error Types](#custom-error-types)
      6. [Error Wrapping Strategy](#error-wrapping-strategy)
      7. [panic and recover](#panic-and-recover)
      8. [Errors in Goroutines](#errors-in-goroutines)
      9. [Multi-Error](#multi-error)
      10. [Test Errors](#test-errors)
      11. [Anti-Patterns](#anti-patterns)
      
      ---
      
      ## Error Basics
      
      ### The error Interface
      
      ```go
      type error interface {
          Error() string
      }
      ```
      
      Any type implementing `Error() string` satisfies the `error` interface.
      
      ### Create Simple Errors
      
      ```go
      import "errors"
      
      var err1 = errors.New("something went wrong")
      
      // fmt.Errorf for formatted messages (no wrapping)
      err2 := fmt.Errorf("user %d not found", id)
      
      // Return nil to signal success
      func divide(a, b float64) (float64, error) {
          if b == 0 {
              return 0, errors.New("division by zero")
          }
          return a / b, nil
      }
      
      // Check error
      result, err := divide(10, 0)
      if err != nil {
          log.Fatal(err)
      }
      ```
      
      ---
      
      ## Wrap Errors with %w
      
      The `%w` verb creates a wrapped error that preserves the original for inspection with `errors.Is` and `errors.As`.
      
      ```go
      func getUser(id int64) (*User, error) {
          row := db.QueryRow("SELECT * FROM users WHERE id = ?", id)
          if err := row.Scan(&user); err != nil {
              return nil, fmt.Errorf("get user %d: %w", id, err)
          }
          return &user, nil
      }
      
      func loadProfile(id int64) (*Profile, error) {
          user, err := getUser(id)
          if err != nil {
              return nil, fmt.Errorf("load profile: %w", err)
          }
          // ...
          return profile, nil
      }
      ```
      
      The resulting error chain looks like:
      
      ```
      load profile: get user 42: sql: no rows in result set
      ```
      
      ### Unwrap the Chain
      
      ```go
      // errors.Unwrap returns the next error in the chain
      wrapped := fmt.Errorf("outer: %w", inner)
      inner == errors.Unwrap(wrapped) // true
      
      // Walk the full chain manually
      for err != nil {
          fmt.Println(err)
          err = errors.Unwrap(err)
      }
      ```
      
      ---
      
      ## errors.Is and errors.As
      
      ### errors.Is — Identity Check
      
      Use `errors.Is` to check whether a specific sentinel error appears anywhere in the chain.
      
      ```go
      var ErrNotFound = errors.New("not found")
      
      err := fmt.Errorf("query: %w", ErrNotFound)
      
      errors.Is(err, ErrNotFound) // true — searches the whole chain
      err == ErrNotFound          // false — direct comparison misses the wrapping
      ```
      
      ### errors.As — Type Check
      
      Use `errors.As` to extract a typed error from anywhere in the chain.
      
      ```go
      type ValidationError struct {
          Field   string
          Message string
      }
      
      func (e *ValidationError) Error() string {
          return fmt.Sprintf("%s: %s", e.Field, e.Message)
      }
      
      err := fmt.Errorf("create user: %w", &ValidationError{Field: "email", Message: "invalid"})
      
      var valErr *ValidationError
      if errors.As(err, &valErr) {
          fmt.Println(valErr.Field)   // "email"
          fmt.Println(valErr.Message) // "invalid"
      }
      ```
      
      ### Custom Is Method
      
      Implement `Is` when equality should be value-based rather than pointer-based.
      
      ```go
      type StatusError struct {
          Code int
      }
      
      func (e *StatusError) Error() string {
          return fmt.Sprintf("status %d", e.Code)
      }
      
      func (e *StatusError) Is(target error) bool {
          t, ok := target.(*StatusError)
          if !ok {
              return false
          }
          return e.Code == t.Code
      }
      
      ErrNotFound := &StatusError{Code: 404}
      
      err := fmt.Errorf("request: %w", &StatusError{Code: 404})
      errors.Is(err, ErrNotFound) // true — matched by value
      ```
      
      ---
      
      ## Sentinel Errors
      
      Sentinel errors are package-level variables used as well-known error values.
      
      ```go
      var (
          ErrNotFound     = errors.New("not found")
          ErrUnauthorized = errors.New("unauthorized")
          ErrConflict     = errors.New("conflict")
      )
      
      func FindUser(id int64) (*User, error) {
          if id == 0 {
              return nil, ErrNotFound
          }
          // ...
      }
      
      // Caller checks identity
      user, err := FindUser(id)
      if errors.Is(err, ErrNotFound) {
          http.Error(w, "Not Found", http.StatusNotFound)
          return
      }
      ```
      
      ### Stdlib Sentinel Examples
      
      ```go
      io.EOF              // end of stream; not an error condition
      io.ErrUnexpectedEOF // stream ended mid-record; is an error
      sql.ErrNoRows       // query returned zero rows
      os.ErrNotExist      // file does not exist (use errors.Is, not ==)
      context.Canceled    // context was cancelled
      context.DeadlineExceeded // context deadline passed
      ```
      
      Note: `os.ErrNotExist` wraps multiple underlying errors (`syscall.ENOENT`, etc.). Always use `errors.Is(err, os.ErrNotExist)` rather than direct comparison.
      
      ---
      
      ## Custom Error Types
      
      ### Struct Error with Extra Fields
      
      ```go
      type NotFoundError struct {
          Resource string
          ID       int64
      }
      
      func (e *NotFoundError) Error() string {
          return fmt.Sprintf("%s with id %d not found", e.Resource, e.ID)
      }
      
      func GetOrder(id int64) (*Order, error) {
          order := findOrder(id)
          if order == nil {
              return nil, &NotFoundError{Resource: "order", ID: id}
          }
          return order, nil
      }
      
      // Extract and use the extra fields
      var notFound *NotFoundError
      if errors.As(err, &notFound) {
          log.Printf("missing resource: %s %d", notFound.Resource, notFound.ID)
      }
      ```
      
      ### HTTPError with Status Code
      
      ```go
      type HTTPError struct {
          Code    int
          Message string
          Cause   error
      }
      
      func (e *HTTPError) Error() string {
          if e.Cause != nil {
              return fmt.Sprintf("HTTP %d: %s: %v", e.Code, e.Message, e.Cause)
          }
          return fmt.Sprintf("HTTP %d: %s", e.Code, e.Message)
      }
      
      func (e *HTTPError) Unwrap() error {
          return e.Cause
      }
      
      // Implement Unwrap to keep the chain intact
      ```
      
      ---
      
      ## Error Wrapping Strategy
      
      ### Add Context at Each Layer
      
      ```go
      // Repository layer: wrap with operation context
      func (r *UserRepo) Find(id int64) (*User, error) {
          var u User
          err := r.db.Get(&u, "SELECT * FROM users WHERE id = $1", id)
          if err != nil {
              return nil, fmt.Errorf("find user %d: %w", id, err)
          }
          return &u, nil
      }
      
      // Service layer: wrap with business operation context
      func (s *UserService) GetProfile(id int64) (*Profile, error) {
          user, err := s.repo.Find(id)
          if err != nil {
              return nil, fmt.Errorf("get profile: %w", err)
          }
          // ...
      }
      
      // Handler layer: inspect and translate for the caller
      func (h *Handler) handleGetProfile(w http.ResponseWriter, r *http.Request) {
          id := parseID(r)
          profile, err := h.svc.GetProfile(id)
          if err != nil {
              if errors.Is(err, sql.ErrNoRows) {
                  http.Error(w, "not found", http.StatusNotFound)
                  return
              }
              http.Error(w, "internal error", http.StatusInternalServerError)
              log.Printf("get profile: %v", err) // log full chain here
              return
          }
          writeJSON(w, profile)
      }
      ```
      
      ### Message Conventions
      
      - Use lowercase for error strings (Go convention)
      - Use `: ` to separate context from cause
      - Do not end with punctuation
      - Do not duplicate information already in the wrapped error
      
      ```go
      // GOOD
      fmt.Errorf("parse config: %w", err)
      fmt.Errorf("connect to database %s: %w", dsn, err)
      
      // BAD: redundant — the wrapped error already says "failed"
      fmt.Errorf("failed to connect: %w", err)
      
      // BAD: capitalized
      fmt.Errorf("Parse config: %w", err)
      ```
      
      ### Log Once, at the Top
      
      ```go
      // BAD: logged at every layer, duplicates output
      func (r *Repo) Find(id int64) (*User, error) {
          err := query()
          if err != nil {
              log.Printf("repo error: %v", err)  // logged here
              return nil, fmt.Errorf("find: %w", err)
          }
      }
      
      func (s *Svc) Get(id int64) (*User, error) {
          u, err := r.Find(id)
          if err != nil {
              log.Printf("svc error: %v", err)   // logged again
              return nil, fmt.Errorf("get: %w", err)
          }
      }
      
      // GOOD: wrap through; log once at the edge (handler/main)
      ```
      
      ---
      
      ## panic and recover
      
      ### When panic Is Legitimate
      
      - Programmer errors that cannot be corrected at runtime (nil dereference, index out of range)
      - Impossible conditions in initialization (`init` or package `var` blocks)
      - Internal consistency violations inside a package (never cross package boundaries)
      
      ```go
      func mustParseURL(raw string) *url.URL {
          u, err := url.Parse(raw)
          if err != nil {
              panic(fmt.Sprintf("invalid hardcoded URL %q: %v", raw, err))
          }
          return u
      }
      
      // Use Must* pattern for hardcoded values only; never for user input
      var baseURL = mustParseURL("https://api.example.com")
      ```
      
      ### recover in HTTP Middleware
      
      ```go
      func recoveryMiddleware(next http.Handler) http.Handler {
          return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
              defer func() {
                  if rec := recover(); rec != nil {
                      // Log with stack trace
                      buf := make([]byte, 4096)
                      n := runtime.Stack(buf, false)
                      log.Printf("panic recovered: %v\n%s", rec, buf[:n])
      
                      http.Error(w, "internal server error", http.StatusInternalServerError)
                  }
              }()
              next.ServeHTTP(w, r)
          })
      }
      ```
      
      ### Do Not recover Across Package Boundaries
      
      A library must never let a panic escape to the caller. Recover internally and return an error.
      
      ```go
      func (p *Parser) Parse(input []byte) (result Result, err error) {
          defer func() {
              if rec := recover(); rec != nil {
                  err = fmt.Errorf("parse panicked: %v", rec)
              }
          }()
          result = p.doParse(input)
          return
      }
      ```
      
      ---
      
      ## Errors in Goroutines
      
      ### Channel-Based
      
      ```go
      func runAsync(ctx context.Context, fn func() error) <-chan error {
          errCh := make(chan error, 1) // buffer of 1 prevents leak
          go func() {
              errCh <- fn()
          }()
          return errCh
      }
      
      errCh := runAsync(ctx, doWork)
      
      select {
      case err := <-errCh:
          if err != nil {
              return fmt.Errorf("async work: %w", err)
          }
      case <-ctx.Done():
          return ctx.Err()
      }
      ```
      
      ### errgroup (Preferred)
      
      ```go
      g, ctx := errgroup.WithContext(ctx)
      
      g.Go(func() error {
          return stepA(ctx)
      })
      g.Go(func() error {
          return stepB(ctx)
      })
      
      // Wait returns the first non-nil error; other goroutines see ctx cancelled
      if err := g.Wait(); err != nil {
          return fmt.Errorf("pipeline: %w", err)
      }
      ```
      
      ### Error Aggregation
      
      When all errors matter (not just the first):
      
      ```go
      type MultiError struct {
          Errors []error
      }
      
      func (m *MultiError) Error() string {
          msgs := make([]string, len(m.Errors))
          for i, e := range m.Errors {
              msgs[i] = e.Error()
          }
          return strings.Join(msgs, "; ")
      }
      
      func runAll(fns []func() error) error {
          var mu sync.Mutex
          var errs []error
          var wg sync.WaitGroup
      
          for _, fn := range fns {
              fn := fn
              wg.Add(1)
              go func() {
                  defer wg.Done()
                  if err := fn(); err != nil {
                      mu.Lock()
                      errs = append(errs, err)
                      mu.Unlock()
                  }
              }()
          }
      
          wg.Wait()
          if len(errs) > 0 {
              return &MultiError{Errors: errs}
          }
          return nil
      }
      ```
      
      ---
      
      ## Multi-Error
      
      ### errors.Join (Go 1.20+)
      
      ```go
      err1 := errors.New("validation failed on field email")
      err2 := errors.New("validation failed on field phone")
      
      combined := errors.Join(err1, err2)
      fmt.Println(combined)
      // validation failed on field email
      // validation failed on field phone
      
      errors.Is(combined, err1) // true
      errors.Is(combined, err2) // true
      ```
      
      ### Collect Validation Errors
      
      ```go
      func validateUser(u User) error {
          var errs []error
      
          if u.Name == "" {
              errs = append(errs, errors.New("name is required"))
          }
          if !isValidEmail(u.Email) {
              errs = append(errs, fmt.Errorf("email %q is invalid", u.Email))
          }
          if u.Age < 0 {
              errs = append(errs, errors.New("age must not be negative"))
          }
      
          return errors.Join(errs...) // nil if errs is empty
      }
      ```
      
      ---
      
      ## Test Errors
      
      ### Check Sentinel Errors with errors.Is
      
      ```go
      func TestFindUser_NotFound(t *testing.T) {
          repo := NewRepo(testDB)
      
          _, err := repo.Find(999)
      
          if !errors.Is(err, ErrNotFound) {
              t.Errorf("expected ErrNotFound, got %v", err)
          }
      }
      ```
      
      ### Extract Typed Errors with errors.As
      
      ```go
      func TestValidate_InvalidEmail(t *testing.T) {
          err := validateUser(User{Name: "Alice", Email: "bad"})
      
          var valErr *ValidationError
          if !errors.As(err, &valErr) {
              t.Fatalf("expected *ValidationError, got %T: %v", err, err)
          }
          if valErr.Field != "email" {
              t.Errorf("expected field 'email', got %q", valErr.Field)
          }
      }
      ```
      
      ### Table-Driven Error Tests
      
      ```go
      func TestDivide(t *testing.T) {
          tests := []struct {
              name    string
              a, b    float64
              wantErr bool
              errIs   error
          }{
              {"normal", 10, 2, false, nil},
              {"divide by zero", 10, 0, true, ErrDivisionByZero},
          }
      
          for _, tt := range tests {
              t.Run(tt.name, func(t *testing.T) {
                  _, err := Divide(tt.a, tt.b)
                  if (err != nil) != tt.wantErr {
                      t.Fatalf("wantErr=%v, got err=%v", tt.wantErr, err)
                  }
                  if tt.errIs != nil && !errors.Is(err, tt.errIs) {
                      t.Errorf("errors.Is(%v, %v) = false", err, tt.errIs)
                  }
              })
          }
      }
      ```
      
      ---
      
      ## Anti-Patterns
      
      ### Stringly-Typed Error Checks
      
      ```go
      // BAD: fragile; breaks on message change
      if err.Error() == "not found" { ... }
      if strings.Contains(err.Error(), "timeout") { ... }
      
      // GOOD: use sentinel or typed errors
      if errors.Is(err, ErrNotFound) { ... }
      
      var netErr *net.OpError
      if errors.As(err, &netErr) && netErr.Timeout() { ... }
      ```
      
      ### Swallowing Errors
      
      ```go
      // BAD: silent discard
      result, _ := doSomething()
      json.Unmarshal(data, &v) // ignoring error
      
      // GOOD: handle or at minimum log
      result, err := doSomething()
      if err != nil {
          return fmt.Errorf("doSomething: %w", err)
      }
      
      if err := json.Unmarshal(data, &v); err != nil {
          return fmt.Errorf("unmarshal response: %w", err)
      }
      ```
      
      ### Log and Return (Double Logging)
      
      ```go
      // BAD: causes duplicate log lines
      func getUser(id int64) (*User, error) {
          user, err := db.Find(id)
          if err != nil {
              log.Printf("db.Find error: %v", err) // logged here
              return nil, fmt.Errorf("find user: %w", err)
          }
          return user, nil
      }
      // caller also logs → same error appears twice
      
      // GOOD: wrap and propagate; log once at the boundary
      func getUser(id int64) (*User, error) {
          user, err := db.Find(id)
          if err != nil {
              return nil, fmt.Errorf("find user %d: %w", id, err)
          }
          return user, nil
      }
      ```
      
      ### Over-Wrapping with Redundant Context
      
      ```go
      // BAD: "failed to" is noise; wrapped error already explains what happened
      return fmt.Errorf("failed to get user: failed to query database: %w", err)
      
      // GOOD: each layer adds one meaningful label
      return fmt.Errorf("get user %d: %w", id, err)
      ```
      
      ### Panic for Expected Errors
      
      ```go
      // BAD: panicking on user-controlled input
      func ParseAge(s string) int {
          n, err := strconv.Atoi(s)
          if err != nil {
              panic("invalid age") // crashes the program
          }
          return n
      }
      
      // GOOD: return the error
      func ParseAge(s string) (int, error) {
          n, err := strconv.Atoi(s)
          if err != nil {
              return 0, fmt.Errorf("parse age %q: %w", s, err)
          }
          return n, nil
      }
      ```
      
      ### Returning Non-nil Error with Non-zero Value
      
      ```go
      // BAD: caller may use the value even when err != nil
      func compute() (Result, error) {
          if bad {
              return Result{partial: true}, errors.New("incomplete")
          }
      }
      
      // GOOD: return zero value on error so callers don't accidentally use it
      func compute() (Result, error) {
          if bad {
              return Result{}, errors.New("incomplete")
          }
      }
      ```
      
    • expert-insights.md 4.3 KB
      # Go HTTP and Service Patterns Reference
      
      ## Table of Contents
      
      1. [HTTP Server (Go 1.22+ Routing)](#1-http-server-go-122-routing)
      2. [Graceful Shutdown](#2-graceful-shutdown)
      3. [HTTP Client Configuration](#3-http-client-configuration)
      4. [JSON Request/Response Helpers](#4-json-requestresponse-helpers)
      
      ---
      
      ## 1. HTTP Server (Go 1.22+ Routing)
      
      Go 1.22 added method matching and path wildcards to `net/http.ServeMux` — no router dependency needed for most services.
      
      ```go
      func main() {
          mux := http.NewServeMux()
      
          mux.HandleFunc("GET /users/{id}", getUser)
          mux.HandleFunc("POST /users", createUser)
      
          server := &http.Server{
              Addr:         ":8080",
              Handler:      mux,
              ReadTimeout:  5 * time.Second,
              WriteTimeout: 10 * time.Second,
              IdleTimeout:  120 * time.Second,
          }
      
          log.Fatal(server.ListenAndServe())
      }
      
      func getUser(w http.ResponseWriter, r *http.Request) {
          id := r.PathValue("id") // Wildcard value from the pattern
      
          user, err := userStore.GetUser(id)
          if err != nil {
              http.Error(w, "User not found", http.StatusNotFound)
              return
          }
      
          w.Header().Set("Content-Type", "application/json")
          json.NewEncoder(w).Encode(user)
      }
      ```
      
      Always set `ReadTimeout`, `WriteTimeout`, and `IdleTimeout` on `http.Server` — the zero values mean no timeout, which leaves the server open to slow-client resource exhaustion.
      
      ## 2. Graceful Shutdown
      
      Drain in-flight requests on SIGINT/SIGTERM instead of dropping them.
      
      ```go
      func main() {
          cfg, err := config.Load()
          if err != nil {
              log.Fatalf("loading config: %v", err)
          }
      
          server := &http.Server{
              Addr:    cfg.Addr,
              Handler: handler.New(cfg),
          }
      
          go func() {
              sigCh := make(chan os.Signal, 1)
              signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
              <-sigCh
      
              ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
              defer cancel()
      
              if err := server.Shutdown(ctx); err != nil {
                  log.Printf("shutdown error: %v", err)
              }
          }()
      
          log.Printf("starting server on %s", cfg.Addr)
          if err := server.ListenAndServe(); err != http.ErrServerClosed {
              log.Fatalf("server error: %v", err)
          }
      }
      ```
      
      `server.Shutdown` stops accepting new connections, then waits for active requests up to the context deadline. Compare against `http.ErrServerClosed` — a clean shutdown returns it, and treating it as fatal masks the difference between intentional and crashed exits.
      
      ## 3. HTTP Client Configuration
      
      Never use `http.DefaultClient` for production calls — it has no timeout. Build a client once and reuse it (the transport pools connections).
      
      ```go
      func NewHTTPClient() *http.Client {
          return &http.Client{
              Timeout: 30 * time.Second,
              Transport: &http.Transport{
                  MaxIdleConns:        100,
                  MaxIdleConnsPerHost: 10,
                  IdleConnTimeout:     90 * time.Second,
              },
          }
      }
      
      func fetchJSON(ctx context.Context, url string, result any) error {
          req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
          if err != nil {
              return err
          }
      
          resp, err := httpClient.Do(req)
          if err != nil {
              return err
          }
          defer resp.Body.Close()
      
          if resp.StatusCode != http.StatusOK {
              return fmt.Errorf("unexpected status: %d", resp.StatusCode)
          }
      
          return json.NewDecoder(resp.Body).Decode(result)
      }
      ```
      
      `MaxIdleConnsPerHost` defaults to 2 — far too low when hammering a single upstream; raise it for service-to-service traffic.
      
      ## 4. JSON Request/Response Helpers
      
      Centralize encoding/decoding so every handler behaves the same.
      
      ```go
      type Response struct {
          Data    any    `json:"data,omitempty"`
          Error   string `json:"error,omitempty"`
          Message string `json:"message,omitempty"`
      }
      
      func writeJSON(w http.ResponseWriter, status int, data any) {
          w.Header().Set("Content-Type", "application/json")
          w.WriteHeader(status)
          json.NewEncoder(w).Encode(data)
      }
      
      func readJSON(r *http.Request, dst any) error {
          dec := json.NewDecoder(r.Body)
          dec.DisallowUnknownFields() // Reject payloads with unexpected keys
      
          if err := dec.Decode(dst); err != nil {
              return fmt.Errorf("decoding JSON: %w", err)
          }
          return nil
      }
      ```
      
      `DisallowUnknownFields` turns silent typos in client payloads (`"emial"`) into explicit 400s instead of zero-valued fields.
      
    • interfaces-generics.md 16.2 KB
      # Go Interfaces and Generics Reference
      
      ## Table of Contents
      
      1. [Interface Design Principles](#1-interface-design-principles)
      2. [Interface Composition](#2-interface-composition)
      3. [Type Assertions](#3-type-assertions)
      4. [Empty Interface and any](#4-empty-interface-and-any)
      5. [Generics Basics](#5-generics-basics)
      6. [Generic Functions](#6-generic-functions)
      7. [Generic Types](#7-generic-types)
      8. [Constraints](#8-constraints)
      9. [When NOT to Use Generics](#9-when-not-to-use-generics)
      10. [Functional Options](#10-functional-options)
      11. [Builder Pattern](#11-builder-pattern)
      12. [Strategy via Interfaces](#12-strategy-via-interfaces)
      
      ---
      
      ## 1. Interface Design Principles
      
      **Accept interfaces, return concrete types.** Callers decide what abstraction they need; implementations should not hide their type behind an interface at the return site.
      
      ```go
      // BAD: returns interface, hides the concrete type unnecessarily
      func NewStore() Store {
          return &postgresStore{}
      }
      
      // GOOD: returns concrete pointer; callers that need the interface accept it
      func NewStore() *PostgresStore {
          return &postgresStore{}
      }
      ```
      
      **Keep interfaces small.** One or two methods is the ideal. Large interfaces are hard to mock and hard to satisfy.
      
      ```go
      // BAD: one interface does too much
      type UserService interface {
          GetUser(id int64) (*User, error)
          CreateUser(u *User) error
          DeleteUser(id int64) error
          SendWelcomeEmail(u *User) error
          AuditLog(action string) error
      }
      
      // GOOD: split by role
      type UserReader interface {
          GetUser(id int64) (*User, error)
      }
      
      type UserWriter interface {
          CreateUser(u *User) error
          DeleteUser(id int64) error
      }
      
      type Notifier interface {
          SendWelcomeEmail(u *User) error
      }
      ```
      
      **Define interfaces at the point of use (consumer), not the provider.** This avoids import cycles and keeps packages decoupled.
      
      Standard library examples of well-sized interfaces:
      
      ```go
      // io package: one method each
      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 }
      
      // fmt package: one method
      type Stringer interface { String() string }
      
      // sort package: three methods (minimum needed for the algorithm)
      type Interface interface {
          Len() int
          Less(i, j int) bool
          Swap(i, j int)
      }
      ```
      
      ---
      
      ## 2. Interface Composition
      
      Embed smaller interfaces to build larger ones. Only embed what callers genuinely need together.
      
      ```go
      // Compose from stdlib primitives
      type ReadWriter interface {
          io.Reader
          io.Writer
      }
      
      type ReadWriteCloser interface {
          io.Reader
          io.Writer
          io.Closer
      }
      
      // Compose from your own interfaces
      type Repository interface {
          UserReader
          UserWriter
      }
      
      // Satisfy a composed interface with one struct
      type postgresStore struct{ db *sql.DB }
      
      func (s *postgresStore) GetUser(id int64) (*User, error)    { /* ... */ }
      func (s *postgresStore) CreateUser(u *User) error           { /* ... */ }
      func (s *postgresStore) DeleteUser(id int64) error          { /* ... */ }
      
      var _ Repository = (*postgresStore)(nil) // compile-time check
      ```
      
      The blank identifier assignment `var _ Repository = (*postgresStore)(nil)` is a zero-cost compile-time assertion that `*postgresStore` satisfies `Repository`.
      
      ---
      
      ## 3. Type Assertions
      
      ### Single-Value Form (Panics on Failure)
      
      Use only when you are certain of the type, such as immediately after a type switch.
      
      ```go
      var v interface{} = "hello"
      s := v.(string) // panics if v is not a string
      ```
      
      ### Comma-OK Form (Safe)
      
      ```go
      var v interface{} = "hello"
      
      s, ok := v.(string)
      if !ok {
          // handle wrong type
      }
      ```
      
      ### Type Switch
      
      The idiomatic way to branch on dynamic type. The variable `x` is narrowed to the concrete type in each case.
      
      ```go
      func describe(v interface{}) string {
          switch x := v.(type) {
          case string:
              return fmt.Sprintf("string of length %d", len(x))
          case int:
              return fmt.Sprintf("int: %d", x)
          case []byte:
              return fmt.Sprintf("bytes: %x", x)
          case fmt.Stringer:
              return fmt.Sprintf("stringer: %s", x.String())
          case nil:
              return "nil"
          default:
              return fmt.Sprintf("unknown type: %T", x)
          }
      }
      ```
      
      ---
      
      ## 4. Empty Interface and any
      
      `any` is an alias for `interface{}` introduced in Go 1.18. Prefer `any` in new code.
      
      Use `any` only when the type is genuinely unknown at compile time: codec targets, generic containers before generics were available, or variadic logging arguments.
      
      ```go
      // Legitimate: JSON decode target unknown at call site
      func Decode(r io.Reader, dst any) error {
          return json.NewDecoder(r).Decode(dst)
      }
      
      // Legitimate: structured logging with arbitrary fields
      func Info(msg string, fields ...any) { /* ... */ }
      
      // Avoid: using any when a concrete type or interface would work
      func Process(v any) {         // BAD if callers always pass *User
          u := v.(*User)            // forced assertion everywhere
      }
      
      func Process(u *User) { /* ... */ } // GOOD
      ```
      
      Do not use `any` as a way to avoid thinking about types. Every `any` is a deferred type error waiting for runtime.
      
      ---
      
      ## 5. Generics Basics
      
      Go generics use type parameters in square brackets. Introduced in Go 1.18.
      
      ```go
      // Type parameter T with constraint comparable
      func Contains[T comparable](slice []T, item T) bool {
          for _, v := range slice {
              if v == item {
                  return true
              }
          }
          return false
      }
      
      // Usage - type inferred from arguments
      found := Contains([]string{"a", "b", "c"}, "b") // true
      found = Contains([]int{1, 2, 3}, 4)              // false
      
      // Multiple type parameters
      func Map[K comparable, V any](m map[K]V, f func(V) V) map[K]V {
          out := make(map[K]V, len(m))
          for k, v := range m {
              out[k] = f(v)
          }
          return out
      }
      ```
      
      Type inference works in most cases. Provide explicit type arguments only when the compiler cannot infer them.
      
      ```go
      // Explicit type argument needed when return type differs from arguments
      func Zero[T any]() T {
          var zero T
          return zero
      }
      
      z := Zero[int]()    // must be explicit: no argument to infer from
      ```
      
      ---
      
      ## 6. Generic Functions
      
      ### Filter, Map, Reduce
      
      ```go
      func Filter[T any](slice []T, predicate func(T) bool) []T {
          var result []T
          for _, v := range slice {
              if predicate(v) {
                  result = append(result, v)
              }
          }
          return result
      }
      
      func Map[T, U any](slice []T, f func(T) U) []U {
          result := make([]U, len(slice))
          for i, v := range slice {
              result[i] = f(v)
          }
          return result
      }
      
      func Reduce[T, U any](slice []T, initial U, f func(U, T) U) U {
          acc := initial
          for _, v := range slice {
              acc = f(acc, v)
          }
          return acc
      }
      
      // Keys returns the keys of a map in unspecified order
      func Keys[K comparable, V any](m map[K]V) []K {
          keys := make([]K, 0, len(m))
          for k := range m {
              keys = append(keys, k)
          }
          return keys
      }
      
      // Values returns the values of a map in unspecified order
      func Values[K comparable, V any](m map[K]V) []V {
          values := make([]V, 0, len(m))
          for _, v := range m {
              values = append(values, v)
          }
          return values
      }
      ```
      
      ---
      
      ## 7. Generic Types
      
      ### Stack
      
      ```go
      type Stack[T any] struct {
          items []T
      }
      
      func (s *Stack[T]) Push(item T) {
          s.items = append(s.items, item)
      }
      
      func (s *Stack[T]) Pop() (T, bool) {
          if len(s.items) == 0 {
              var zero T
              return zero, false
          }
          n := len(s.items) - 1
          item := s.items[n]
          s.items = s.items[:n]
          return item, true
      }
      
      func (s *Stack[T]) Len() int { return len(s.items) }
      ```
      
      ### Result Type
      
      Encode success or failure without error returns scattered through call sites.
      
      ```go
      type Result[T any] struct {
          value T
          err   error
      }
      
      func Ok[T any](value T) Result[T]    { return Result[T]{value: value} }
      func Err[T any](err error) Result[T] { return Result[T]{err: err} }
      
      func (r Result[T]) Unwrap() (T, error) { return r.value, r.err }
      
      func (r Result[T]) Must() T {
          if r.err != nil {
              panic(r.err)
          }
          return r.value
      }
      ```
      
      ### Generic Cache with TTL
      
      ```go
      type entry[V any] struct {
          value     V
          expiresAt time.Time
      }
      
      type Cache[K comparable, V any] struct {
          mu   sync.RWMutex
          data map[K]entry[V]
          ttl  time.Duration
      }
      
      func NewCache[K comparable, V any](ttl time.Duration) *Cache[K, V] {
          return &Cache[K, V]{data: make(map[K]entry[V]), ttl: ttl}
      }
      
      func (c *Cache[K, V]) Set(key K, value V) {
          c.mu.Lock()
          defer c.mu.Unlock()
          c.data[key] = entry[V]{value: value, expiresAt: time.Now().Add(c.ttl)}
      }
      
      func (c *Cache[K, V]) Get(key K) (V, bool) {
          c.mu.RLock()
          defer c.mu.RUnlock()
          e, ok := c.data[key]
          if !ok || time.Now().After(e.expiresAt) {
              var zero V
              return zero, false
          }
          return e.value, true
      }
      ```
      
      ---
      
      ## 8. Constraints
      
      ### Built-In Constraints
      
      ```go
      // comparable: supports == and != (maps, channels, basic types, structs of comparable fields)
      func Index[T comparable](slice []T, item T) int {
          for i, v := range slice {
              if v == item {
                  return i
              }
          }
          return -1
      }
      
      // any: no constraint, widest possible
      func Ptr[T any](v T) *T { return &v }
      ```
      
      ### golang.org/x/exp/constraints
      
      ```go
      import "golang.org/x/exp/constraints"
      
      // Ordered: all types that support <, <=, >, >=
      func Min[T constraints.Ordered](a, b T) T {
          if a < b {
              return a
          }
          return b
      }
      
      func Max[T constraints.Ordered](a, b T) T {
          if a > b {
              return a
          }
          return b
      }
      
      func Clamp[T constraints.Ordered](v, lo, hi T) T {
          return Min(Max(v, lo), hi)
      }
      ```
      
      ### Custom Constraints
      
      ```go
      // Union of specific types
      type Integer interface {
          ~int | ~int8 | ~int16 | ~int32 | ~int64
      }
      
      type Float interface {
          ~float32 | ~float64
      }
      
      type Number interface {
          Integer | Float
      }
      
      func Sum[T Number](nums []T) T {
          var total T
          for _, n := range nums {
              total += n
          }
          return total
      }
      ```
      
      ### Tilde (~) for Underlying Types
      
      `~T` includes all types whose underlying type is `T`. Without `~`, named types are excluded.
      
      ```go
      type Celsius float64
      type Fahrenheit float64
      
      // Without ~: Celsius and Fahrenheit do not satisfy Float
      type Float interface { float32 | float64 }
      
      // With ~: Celsius and Fahrenheit satisfy ~float64
      type Float interface { ~float32 | ~float64 }
      
      func Convert[T ~float64](v T) T { return v * 9 / 5 + 32 }
      
      c := Celsius(100)
      f := Convert(c) // works because ~float64 includes Celsius
      ```
      
      ---
      
      ## 9. When NOT to Use Generics
      
      **Use an interface when behavior varies by type.** Generics parametrize over structure, not behavior. If the algorithm calls different methods depending on the type, use an interface.
      
      ```go
      // BAD: generics cannot help here - behavior is type-specific
      func Process[T any](v T) {
          // Cannot call v.Serialize() without a constraint defining it
      }
      
      // GOOD: interface captures the varying behavior
      type Processor interface {
          Process() error
      }
      func Run(p Processor) error { return p.Process() }
      ```
      
      **Use a concrete type when you only have one type.** Adding a type parameter for a function that only ever handles `string` or `int` adds noise with no benefit.
      
      ```go
      // Unnecessary generics
      func ParseInt[T ~string](s T) (int64, error) {
          return strconv.ParseInt(string(s), 10, 64)
      }
      
      // Simpler and clearer
      func ParseInt(s string) (int64, error) {
          return strconv.ParseInt(s, 10, 64)
      }
      ```
      
      **Prefer `any` + type switch for heterogeneous collections** where types are enumerable and fixed. Generics do not simplify this case.
      
      ---
      
      ## 10. Functional Options
      
      The functional options pattern gives constructors optional, named parameters with default values and forward compatibility.
      
      ```go
      type Server struct {
          host    string
          port    int
          timeout time.Duration
          maxConn int
          logger  *slog.Logger
      }
      
      type Option func(*Server) error
      
      func WithHost(host string) Option {
          return func(s *Server) error {
              if host == "" {
                  return errors.New("host cannot be empty")
              }
              s.host = host
              return nil
          }
      }
      
      func WithPort(port int) Option {
          return func(s *Server) error {
              if port < 1 || port > 65535 {
                  return fmt.Errorf("invalid port: %d", port)
              }
              s.port = port
              return nil
          }
      }
      
      func WithTimeout(d time.Duration) Option {
          return func(s *Server) error {
              if d <= 0 {
                  return errors.New("timeout must be positive")
              }
              s.timeout = d
              return nil
          }
      }
      
      func WithLogger(l *slog.Logger) Option {
          return func(s *Server) error {
              s.logger = l
              return nil
          }
      }
      
      func NewServer(opts ...Option) (*Server, error) {
          s := &Server{ // defaults
              host:    "localhost",
              port:    8080,
              timeout: 30 * time.Second,
              maxConn: 100,
              logger:  slog.Default(),
          }
          for _, opt := range opts {
              if err := opt(s); err != nil {
                  return nil, fmt.Errorf("applying option: %w", err)
              }
          }
          return s, nil
      }
      
      // Usage
      srv, err := NewServer(
          WithHost("0.0.0.0"),
          WithPort(9090),
          WithTimeout(time.Minute),
      )
      ```
      
      ---
      
      ## 11. Builder Pattern
      
      Use when construction requires many steps and partial construction is meaningful.
      
      ```go
      type QueryBuilder struct {
          table   string
          columns []string
          where   []string
          orderBy string
          limit   int
          args    []any
          err     error // carry errors through the chain
      }
      
      func NewQuery(table string) *QueryBuilder {
          if table == "" {
              return &QueryBuilder{err: errors.New("table name required")}
          }
          return &QueryBuilder{table: table, columns: []string{"*"}}
      }
      
      func (q *QueryBuilder) Select(cols ...string) *QueryBuilder {
          if q.err != nil {
              return q
          }
          q.columns = cols
          return q
      }
      
      func (q *QueryBuilder) Where(condition string, args ...any) *QueryBuilder {
          if q.err != nil {
              return q
          }
          q.where = append(q.where, condition)
          q.args = append(q.args, args...)
          return q
      }
      
      func (q *QueryBuilder) OrderBy(col string) *QueryBuilder {
          if q.err != nil {
              return q
          }
          q.orderBy = col
          return q
      }
      
      func (q *QueryBuilder) Limit(n int) *QueryBuilder {
          if q.err != nil {
              return q
          }
          if n < 0 {
              q.err = fmt.Errorf("limit must be non-negative, got %d", n)
              return q
          }
          q.limit = n
          return q
      }
      
      func (q *QueryBuilder) Build() (string, []any, error) {
          if q.err != nil {
              return "", nil, q.err
          }
          // assemble SQL from q.table, q.columns, q.where, q.orderBy, q.limit
          sql := fmt.Sprintf("SELECT %s FROM %s", strings.Join(q.columns, ", "), q.table)
          if len(q.where) > 0 {
              sql += " WHERE " + strings.Join(q.where, " AND ")
          }
          if q.orderBy != "" {
              sql += " ORDER BY " + q.orderBy
          }
          if q.limit > 0 {
              sql += fmt.Sprintf(" LIMIT %d", q.limit)
          }
          return sql, q.args, nil
      }
      
      // Usage
      sql, args, err := NewQuery("users").
          Select("id", "name", "email").
          Where("active = $1", true).
          Where("role = $2", "admin").
          OrderBy("name").
          Limit(25).
          Build()
      ```
      
      ---
      
      ## 12. Strategy via Interfaces
      
      Swap algorithms at runtime by accepting an interface. The caller chooses the strategy; the function does not need to know the implementation.
      
      ```go
      // Define the strategy interface
      type Hasher interface {
          Hash(data []byte) []byte
          Name() string
      }
      
      // Multiple implementations
      type SHA256Hasher struct{}
      
      func (SHA256Hasher) Hash(data []byte) []byte {
          h := sha256.Sum256(data)
          return h[:]
      }
      func (SHA256Hasher) Name() string { return "sha256" }
      
      type Blake2Hasher struct{}
      
      func (Blake2Hasher) Hash(data []byte) []byte {
          h := blake2b.Sum256(data)
          return h[:]
      }
      func (Blake2Hasher) Name() string { return "blake2b" }
      
      // Consumer accepts the interface - does not care about the algorithm
      type FileStore struct {
          hasher Hasher
      }
      
      func NewFileStore(h Hasher) *FileStore {
          return &FileStore{hasher: h}
      }
      
      func (fs *FileStore) Store(path string, data []byte) error {
          checksum := fs.hasher.Hash(data)
          // write data and checksum to path
          return writeWithChecksum(path, data, checksum, fs.hasher.Name())
      }
      
      // Swap strategies at call site
      fastStore := NewFileStore(Blake2Hasher{})
      secureStore := NewFileStore(SHA256Hasher{})
      ```
      
      This pattern is the Go equivalent of the Gang of Four Strategy pattern. It composes without inheritance and is trivially testable: inject a `mockHasher` that returns fixed bytes.
      
    • performance.md 17.3 KB
      # Go Performance Reference
      
      ## Table of Contents
      
      1. [pprof](#pprof)
      2. [go tool trace](#go-tool-trace)
      3. [Benchmarks](#benchmarks)
      4. [Escape Analysis](#escape-analysis)
      5. [Memory Optimization](#memory-optimization)
      6. [String Performance](#string-performance)
      7. [Struct Alignment](#struct-alignment)
      8. [Map Performance](#map-performance)
      9. [I/O Performance](#io-performance)
      10. [Inlining](#inlining)
      11. [Common Performance Anti-Patterns](#common-performance-anti-patterns)
      
      ---
      
      ## pprof
      
      ### Enable pprof in a Production Server
      
      ```go
      import (
          "net/http"
          _ "net/http/pprof"  // Side-effect import registers handlers on DefaultServeMux
      )
      
      func main() {
          // Serve pprof on a separate port — never expose this publicly
          go func() {
              log.Println(http.ListenAndServe("localhost:6060", nil))
          }()
      
          // ... start your actual server
      }
      ```
      
      ### Collect and Analyze CPU Profiles
      
      ```bash
      # 30-second CPU profile from a running server
      go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
      
      # Inside pprof interactive shell
      (pprof) top10          # Top 10 functions by CPU
      (pprof) list myFunc    # Annotated source for a function
      (pprof) web            # Open flame graph in browser (requires graphviz)
      (pprof) png > cpu.png  # Export to image
      ```
      
      ### Collect and Analyze Memory Profiles
      
      ```bash
      # Heap profile (in-use allocations)
      go tool pprof http://localhost:6060/debug/pprof/heap
      
      # Allocation profile (all allocations since start)
      go tool pprof http://localhost:6060/debug/pprof/allocs
      
      # Inside pprof
      (pprof) top            # Top allocators
      (pprof) inuse_space    # Sort by in-use bytes
      (pprof) alloc_objects  # Sort by allocation count
      ```
      
      ### Profile Goroutines
      
      ```bash
      # Goroutine profile — shows all running goroutines with stack traces
      go tool pprof http://localhost:6060/debug/pprof/goroutine
      
      # Or view in browser for a quick human-readable dump
      curl http://localhost:6060/debug/pprof/goroutine?debug=2
      ```
      
      ### Write Profiles Programmatically
      
      ```go
      import "runtime/pprof"
      
      // CPU profile
      f, _ := os.Create("cpu.prof")
      pprof.StartCPUProfile(f)
      defer pprof.StopCPUProfile()
      
      // Memory profile (write at end of program or specific checkpoint)
      f, _ := os.Create("mem.prof")
      runtime.GC()  // Force GC for accurate snapshot
      pprof.WriteHeapProfile(f)
      f.Close()
      ```
      
      ### Compare Two Profiles
      
      ```bash
      # Capture baseline and after a change, then diff them
      go tool pprof -base baseline.prof current.prof
      ```
      
      ---
      
      ## go tool trace
      
      The tracer records goroutine scheduling, GC pauses, and syscalls at microsecond resolution.
      
      ### Record a Trace
      
      ```bash
      # From a live server
      curl http://localhost:6060/debug/pprof/trace?seconds=5 > trace.out
      
      # Or programmatically
      import "runtime/trace"
      
      f, _ := os.Create("trace.out")
      trace.Start(f)
      defer trace.Stop()
      ```
      
      ### Analyze a Trace
      
      ```bash
      go tool trace trace.out   # Opens browser-based UI
      ```
      
      Key views in the UI:
      
      - **Goroutine analysis**: Which goroutines ran, for how long, what blocked them
      - **View trace**: Timeline of all goroutines across P (processor) threads
      - **Minimum mutator utilization (MMU)**: Percentage of time your code ran vs GC
      
      ### Identify Scheduling Latency
      
      Look for goroutines spending time in "Runnable" state — this means they are ready to run but waiting for a P. Signs of over-subscription: too many goroutines competing for `GOMAXPROCS` slots.
      
      ```go
      // Instrument specific regions in the trace
      import "runtime/trace"
      
      ctx, task := trace.NewTask(ctx, "processOrder")
      defer task.End()
      
      trace.WithRegion(ctx, "validateInput", func() {
          validate(input)
      })
      ```
      
      ---
      
      ## Benchmarks
      
      ### Write Effective Benchmarks
      
      ```go
      func BenchmarkProcess(b *testing.B) {
          data := generateLargeInput()  // Setup before timer
      
          b.ResetTimer()  // Exclude setup from measurement
          for i := 0; i < b.N; i++ {
              Process(data)
          }
      }
      ```
      
      ### Use b.StopTimer / b.StartTimer for Per-Iteration Setup
      
      ```go
      func BenchmarkSort(b *testing.B) {
          for i := 0; i < b.N; i++ {
              b.StopTimer()
              data := generateUnsortedSlice(1000)  // Re-create per iteration
              b.StartTimer()
      
              sort.Ints(data)
          }
      }
      ```
      
      ### Allocations Matter — Report Them
      
      ```go
      func BenchmarkParse(b *testing.B) {
          b.ReportAllocs()  // Show allocs/op and B/op in output
          for i := 0; i < b.N; i++ {
              Parse(input)
          }
      }
      ```
      
      ### Run Benchmarks
      
      ```bash
      go test -bench=. -benchmem -count=5 ./...
      
      # Run only matching benchmarks
      go test -bench=BenchmarkProcess -benchmem -run=^$ ./pkg/processor
      
      # -run=^$ suppresses tests, runs only benchmarks
      ```
      
      ### Compare Results with benchstat
      
      ```bash
      go install golang.org/x/perf/cmd/benchstat@latest
      
      # Capture two runs
      go test -bench=. -count=10 ./... > before.txt
      # Make your change
      go test -bench=. -count=10 ./... > after.txt
      
      benchstat before.txt after.txt
      ```
      
      Output shows statistical significance: `p < 0.05` means the difference is likely real, not noise. Use `-count=10` or more for reliable statistics.
      
      ---
      
      ## Escape Analysis
      
      ### Inspect Escape Decisions
      
      ```bash
      go build -gcflags='-m' ./...         # Basic escape analysis
      go build -gcflags='-m=2' ./...       # Verbose (shows escape reason)
      go test -gcflags='-m' ./...          # On test files
      ```
      
      ### Understand Heap vs Stack
      
      Values escape to the heap when:
      
      - Their address is returned or stored in a longer-lived structure
      - They are assigned to an interface
      - They are too large for the stack (default stack starts at 8KB, goroutines grow as needed but large locals still escape)
      - The compiler cannot prove the lifetime is bounded
      
      ```go
      // Stack allocated — does NOT escape
      func sumSquares(nums []int) int {
          total := 0  // total stays on stack
          for _, n := range nums {
              total += n * n
          }
          return total
      }
      
      // Heap allocated — escapes because address is returned
      func newCounter() *int {
          n := 0
          return &n  // n escapes: "moved to heap: n"
      }
      
      // Interface assignment causes escape
      func logValue(v interface{}) {  // Passing int here allocates on heap
          fmt.Println(v)
      }
      ```
      
      ### Reduce Allocations with Value Receivers
      
      ```go
      // BAD: Pointer causes allocation when assigned to interface
      type Point struct{ X, Y float64 }
      func (p *Point) String() string { return fmt.Sprintf("(%f, %f)", p.X, p.Y) }
      
      // GOOD: Value receiver, may stay on stack
      func (p Point) String() string { return fmt.Sprintf("(%f, %f)", p.X, p.Y) }
      ```
      
      ---
      
      ## Memory Optimization
      
      ### Use sync.Pool for Frequently Allocated Short-Lived Objects
      
      ```go
      var bufPool = sync.Pool{
          New: func() interface{} {
              return new(bytes.Buffer)
          },
      }
      
      func formatMessage(data []byte) string {
          buf := bufPool.Get().(*bytes.Buffer)
          defer func() {
              buf.Reset()
              bufPool.Put(buf)
          }()
      
          buf.Write(data)
          // ... format into buf
          return buf.String()
      }
      ```
      
      Pool objects may be collected by GC at any time. Never store state that must survive across GC cycles in a pool.
      
      ### Pre-Allocate Slices
      
      ```go
      // BAD: O(n) reallocations as slice grows
      var results []Result
      for _, item := range items {
          results = append(results, process(item))
      }
      
      // GOOD: Single allocation
      results := make([]Result, 0, len(items))
      for _, item := range items {
          results = append(results, process(item))
      }
      ```
      
      ### Reuse Slices Across Calls
      
      ```go
      type Processor struct {
          buf []byte  // Reused across calls
      }
      
      func (p *Processor) Process(input []byte) []byte {
          p.buf = p.buf[:0]           // Reset length, keep capacity
          p.buf = append(p.buf, input...)
          // ... transform p.buf
          return p.buf
      }
      ```
      
      ### Avoid Large Value Copies
      
      ```go
      type LargeStruct struct {
          Data [4096]byte
          // ...
      }
      
      // BAD: Copies 4KB on every call
      func processLarge(s LargeStruct) { ... }
      
      // GOOD: Pass pointer
      func processLarge(s *LargeStruct) { ... }
      ```
      
      ---
      
      ## String Performance
      
      ### Use strings.Builder for Concatenation
      
      ```go
      // BAD: Creates a new string on every iteration
      var result string
      for _, s := range parts {
          result += s + ", "
      }
      
      // GOOD: Single allocation
      var sb strings.Builder
      sb.Grow(estimatedSize)  // Pre-grow if you know the size
      for _, s := range parts {
          sb.WriteString(s)
          sb.WriteString(", ")
      }
      result := sb.String()
      ```
      
      ### Convert Between []byte and string Without Allocation
      
      The standard `string(b)` and `[]byte(s)` conversions always allocate. For read-only access within a single goroutine, use `unsafe`:
      
      ```go
      import "unsafe"
      
      // []byte to string — zero copy, safe only if you don't modify b afterward
      func bytesToString(b []byte) string {
          return unsafe.String(unsafe.SliceData(b), len(b))
      }
      
      // string to []byte — zero copy, safe only for reads
      func stringToBytes(s string) []byte {
          return unsafe.Slice(unsafe.StringData(s), len(s))
      }
      ```
      
      These are valid as of Go 1.20. Do not use the older `*(*string)(unsafe.Pointer(&b))` pattern.
      
      ### Avoid fmt.Sprintf for Simple Concatenation
      
      ```go
      // BAD: Heap allocation, format parsing overhead
      key := fmt.Sprintf("%s:%d", prefix, id)
      
      // GOOD: strconv is faster for basic conversions
      key := prefix + ":" + strconv.Itoa(id)
      
      // GOOD: For multiple parts, strings.Join or Builder
      key := strings.Join([]string{prefix, strconv.Itoa(id)}, ":")
      ```
      
      ---
      
      ## Struct Alignment
      
      The CPU reads memory in aligned chunks. Padding bytes are inserted to satisfy alignment requirements. Reordering fields from largest to smallest eliminates wasted bytes.
      
      ```go
      // BAD: 24 bytes due to padding
      type BadLayout struct {
          Active  bool    // 1 byte + 7 bytes padding
          Count   int64   // 8 bytes
          Flag    bool    // 1 byte + 7 bytes padding
      }
      
      // GOOD: 16 bytes, no padding
      type GoodLayout struct {
          Count   int64   // 8 bytes
          Active  bool    // 1 byte
          Flag    bool    // 1 byte + 6 bytes padding (to align to 8)
      }
      ```
      
      ### Check Sizes and Padding
      
      ```go
      import "unsafe"
      
      fmt.Println(unsafe.Sizeof(BadLayout{}))   // 24
      fmt.Println(unsafe.Sizeof(GoodLayout{}))  // 16
      ```
      
      ### Use fieldalignment to Find Problems Automatically
      
      ```bash
      go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest
      
      fieldalignment ./...         # Report structs with inefficient layout
      fieldalignment -fix ./...    # Rewrite fields automatically
      ```
      
      ### Cache Line Considerations for Concurrent Structs
      
      Fields accessed by different goroutines should be on separate cache lines (64 bytes) to prevent false sharing:
      
      ```go
      type Counters struct {
          reads  int64
          _      [56]byte  // Pad to fill cache line
          writes int64
      }
      ```
      
      ---
      
      ## Map Performance
      
      ### Pre-Size Maps
      
      ```go
      // BAD: Map grows incrementally, triggering multiple rehashes
      m := make(map[string]int)
      for _, item := range items {
          m[item.Key] = item.Value
      }
      
      // GOOD: Single allocation
      m := make(map[string]int, len(items))
      for _, item := range items {
          m[item.Key] = item.Value
      }
      ```
      
      ### Use Switch for Small Key Sets
      
      For fewer than ~8 fixed string keys, a switch statement is faster than a map due to branch prediction and no hashing overhead:
      
      ```go
      // Faster for small, known sets
      func httpMethodCode(method string) int {
          switch method {
          case "GET":    return 0
          case "POST":   return 1
          case "PUT":    return 2
          case "DELETE": return 3
          default:       return -1
          }
      }
      ```
      
      ### Choose sync.Map vs Sharded Map
      
      `sync.Map` is optimized for two specific cases:
      1. Write-once, read-many (mostly reads after initial population)
      2. Many goroutines reading/writing disjoint keys
      
      For general concurrent access with frequent writes, a sharded map with per-shard mutexes outperforms `sync.Map`:
      
      ```go
      const numShards = 256
      
      type ShardedMap struct {
          shards [numShards]struct {
              sync.RWMutex
              m map[string]interface{}
          }
      }
      
      func (sm *ShardedMap) shard(key string) int {
          h := fnv.New32()
          h.Write([]byte(key))
          return int(h.Sum32()) % numShards
      }
      
      func (sm *ShardedMap) Get(key string) (interface{}, bool) {
          s := &sm.shards[sm.shard(key)]
          s.RLock()
          v, ok := s.m[key]
          s.RUnlock()
          return v, ok
      }
      ```
      
      ---
      
      ## I/O Performance
      
      ### Always Wrap with bufio
      
      Unbuffered reads and writes issue a syscall for every call. Buffering batches them:
      
      ```go
      // BAD: Syscall per line
      f, _ := os.Open("data.txt")
      scanner := bufio.NewScanner(f)  // This is already buffered — correct
      
      // BAD: Syscall per Write call
      f, _ := os.Create("out.txt")
      fmt.Fprintln(f, line)  // Goes through direct write
      
      // GOOD: Buffered writes
      f, _ := os.Create("out.txt")
      bw := bufio.NewWriterSize(f, 64*1024)  // 64KB buffer
      defer bw.Flush()
      fmt.Fprintln(bw, line)
      ```
      
      ### Use io.Copy for Efficient Transfers
      
      `io.Copy` uses a 32KB internal buffer and delegates to `sendfile(2)` or `splice(2)` when both sides support it (e.g., `*os.File` to `*net.TCPConn`):
      
      ```go
      // Efficient file download with no intermediate allocation
      func serveFile(w http.ResponseWriter, path string) error {
          f, err := os.Open(path)
          if err != nil {
              return err
          }
          defer f.Close()
      
          _, err = io.Copy(w, f)
          return err
      }
      ```
      
      ### Use io.Pipe for Producer-Consumer Pipelines
      
      ```go
      pr, pw := io.Pipe()
      
      go func() {
          defer pw.Close()
          json.NewEncoder(pw).Encode(largeStruct)  // Streams without buffering whole JSON
      }()
      
      http.Post(url, "application/json", pr)
      ```
      
      ### Limit Reads to Avoid Memory Exhaustion
      
      ```go
      const maxBodySize = 1 << 20  // 1MB
      
      r.Body = http.MaxBytesReader(w, r.Body, maxBodySize)
      if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
          http.Error(w, "request too large or invalid", http.StatusBadRequest)
          return
      }
      ```
      
      ---
      
      ## Inlining
      
      The compiler inlines small functions to eliminate call overhead. A function is inlined when its "cost" (an internal AST node count) stays below a threshold (~80 nodes).
      
      ### Check What Gets Inlined
      
      ```bash
      go build -gcflags='-m' ./...
      
      # Output includes:
      # ./pkg/math.go:12:6: can inline Add
      # ./pkg/handler.go:45:12: inlining call to Add
      # ./pkg/handler.go:60:5: cannot inline processLarge: function too complex
      ```
      
      ### Write Inlineable Functions
      
      ```go
      // Inlineable: small, no closures, no defer
      func clamp(v, min, max int) int {
          if v < min { return min }
          if v > max { return max }
          return v
      }
      
      // NOT inlineable: contains a closure
      func makeAdder(n int) func(int) int {
          return func(x int) int { return x + n }
      }
      ```
      
      ### Prevent Inlining
      
      ```go
      //go:noinline  // Force a function to never be inlined (useful for benchmarking)
      func expensiveOperation(data []byte) Result {
          // ...
      }
      ```
      
      Use `//go:noinline` in benchmarks when you want to measure the cost of a function call itself, or to prevent the compiler from optimizing away a call you want to measure.
      
      ---
      
      ## Common Performance Anti-Patterns
      
      ### Reflection in Hot Paths
      
      Reflection bypasses type-system optimizations, performs map lookups, and allocates. Avoid in code called frequently:
      
      ```go
      // BAD: reflect.ValueOf allocates, method lookup is slow
      func setField(obj interface{}, name string, value interface{}) {
          v := reflect.ValueOf(obj).Elem()
          v.FieldByName(name).Set(reflect.ValueOf(value))
      }
      
      // GOOD: Generated code or type switch
      func applyUpdate(u *User, field string, value interface{}) {
          switch field {
          case "Name":  u.Name = value.(string)
          case "Email": u.Email = value.(string)
          }
      }
      ```
      
      ### fmt.Sprintf in Hot Paths
      
      `fmt.Sprintf` parses a format string, uses reflection, and typically allocates:
      
      ```go
      // BAD in hot path
      key := fmt.Sprintf("user:%d:session:%s", userID, sessionID)
      
      // GOOD: strconv + concatenation
      key := "user:" + strconv.FormatInt(userID, 10) + ":session:" + sessionID
      
      // GOOD for complex formatting: pre-build a template or use strings.Builder
      ```
      
      ### Unnecessary Allocations in Loops
      
      ```go
      // BAD: Allocates a new map every iteration
      for _, item := range items {
          m := map[string]int{"count": item.Count}
          process(m)
      }
      
      // GOOD: Allocate once, reuse
      m := make(map[string]int, 1)
      for _, item := range items {
          m["count"] = item.Count
          process(m)
          // Clear before next iteration if needed
          for k := range m { delete(m, k) }
      }
      ```
      
      ### Goroutine Leak from Unclosed Channels
      
      ```go
      // BAD: Goroutine blocked forever if consumer exits early
      func generate(nums ...int) <-chan int {
          out := make(chan int)
          go func() {
              for _, n := range nums {
                  out <- n  // Blocks forever if nobody reads
              }
              close(out)
          }()
          return out
      }
      
      // GOOD: Use context for cancellation
      func generate(ctx context.Context, nums ...int) <-chan int {
          out := make(chan int, len(nums))
          go func() {
              defer close(out)
              for _, n := range nums {
                  select {
                  case out <- n:
                  case <-ctx.Done():
                      return
                  }
              }
          }()
          return out
      }
      ```
      
      ### Copying a Mutex
      
      Mutexes must not be copied after first use. Copying a locked mutex will deadlock; copying an unlocked mutex silently creates a new, independent lock:
      
      ```go
      // BAD: Copies the mutex
      type Cache struct{ mu sync.Mutex; data map[string]int }
      func copyCache(c Cache) Cache { return c }  // Copies mu — wrong
      
      // GOOD: Always pass and return pointers for types containing mutexes
      func processCache(c *Cache) { ... }
      ```
      
      ### Defer in a Tight Loop
      
      Defers execute at function return, not loop iteration. Inside a loop, defers pile up and all run together at the end:
      
      ```go
      // BAD: All files stay open until the function returns
      for _, path := range paths {
          f, _ := os.Open(path)
          defer f.Close()  // Runs at function exit, not loop end
          process(f)
      }
      
      // GOOD: Wrap in a closure or extract to a helper
      for _, path := range paths {
          func() {
              f, _ := os.Open(path)
              defer f.Close()  // Now runs at end of this closure
              process(f)
          }()
      }
      ```
      
    • project-structure.md 12.6 KB
      # Go Project Structure Reference
      
      ## Table of Contents
      
      1. [Standard Project Layout](#standard-project-layout)
      2. [Module Management](#module-management)
      3. [Workspace Mode](#workspace-mode)
      4. [Build Tags](#build-tags)
      5. [Build Configuration](#build-configuration)
      6. [Makefile and Justfile Patterns](#makefile-and-justfile-patterns)
      7. [Linting](#linting)
      8. [Code Generation](#code-generation)
      9. [Release](#release)
      
      ---
      
      ## Standard Project Layout
      
      The Go community has converged on a layout that separates public, private, and executable code clearly.
      
      ```
      myapp/
      ├── cmd/                    # Executable entry points (one dir per binary)
      │   ├── server/
      │   │   └── main.go
      │   └── worker/
      │       └── main.go
      ├── internal/               # Private packages (import-restricted by go toolchain)
      │   ├── config/
      │   │   └── config.go
      │   ├── handler/
      │   │   └── user.go
      │   ├── service/
      │   │   └── user.go
      │   └── repository/
      │       └── user.go
      ├── pkg/                    # Public packages (importable by external projects)
      │   └── validator/
      │       └── validator.go
      ├── api/                    # API definitions (OpenAPI, protobuf, gRPC)
      │   └── openapi.yaml
      ├── web/                    # Web assets, templates
      ├── scripts/                # Build, install, CI scripts
      ├── configs/                # Config file templates
      ├── testdata/               # Test fixtures (go tools ignore dirs starting with "testdata")
      ├── go.mod
      ├── go.sum
      ├── Makefile (or justfile)
      └── README.md
      ```
      
      ### When to Use Each Directory
      
      **cmd/**: Place `main.go` files here. Each subdirectory is a separate binary. Keep main.go thin — parse flags, load config, wire dependencies, then call into `internal/`.
      
      **internal/**: Use for everything application-specific. The Go toolchain enforces that packages under `internal/` can only be imported by code in the parent directory tree. Use this for business logic, handlers, database access.
      
      **pkg/**: Only create this if you genuinely want external projects to import your code. Most applications do not need `pkg/` at all. Avoid the anti-pattern of putting everything in `pkg/` just to follow the template.
      
      ```go
      // cmd/server/main.go — wire dependencies here, logic lives in internal/
      func main() {
          cfg, err := config.Load()
          if err != nil {
              log.Fatalf("loading config: %v", err)
          }
      
          db, err := database.Connect(cfg.DatabaseURL)
          if err != nil {
              log.Fatalf("connecting to database: %v", err)
          }
      
          repo := repository.NewUser(db)
          svc := service.NewUser(repo)
          h := handler.NewUser(svc)
      
          srv := &http.Server{Addr: cfg.Addr, Handler: h.Routes()}
          log.Fatal(srv.ListenAndServe())
      }
      ```
      
      ---
      
      ## Module Management
      
      ### go.mod Directives
      
      ```
      module github.com/myorg/myapp
      
      go 1.22
      
      require (
          github.com/lib/pq v1.10.9
          golang.org/x/sync v0.6.0
      )
      
      // Replace a dependency with a local version during development
      replace github.com/myorg/shared => ../shared
      
      // Exclude a specific broken version
      exclude github.com/bad/module v1.2.3
      ```
      
      **require**: Direct and indirect dependencies. The `// indirect` comment marks transitive dependencies that aren't directly imported by your code but are required by your dependencies.
      
      **replace**: Use for local development of shared modules, or to patch a dependency without forking. Remove before merging to main — `replace` directives break downstream consumers.
      
      **exclude**: Prevents a specific version from being selected by MVS. Useful when a version has a known bug and you want to force a later version.
      
      ### Manage go.sum
      
      `go.sum` contains the expected cryptographic checksums of module content. Commit it to version control. Never edit it manually. Regenerate with:
      
      ```bash
      go mod tidy        # Add missing, remove unused dependencies
      go mod verify      # Verify checksums against go.sum
      go mod download    # Pre-download modules (useful in Docker layers)
      ```
      
      ### Private Modules
      
      Configure the Go toolchain to skip the public checksum database and proxy for private code:
      
      ```bash
      # Tell go to bypass proxy and sumdb for private modules
      export GOPRIVATE=github.com/myorg/*
      
      # Separate controls for proxy and sumdb
      export GONOSUMCHECK=github.com/myorg/*
      export GONOPROXY=github.com/myorg/*
      
      # Use a corporate proxy for public modules
      export GOPROXY=https://proxy.company.com,direct
      ```
      
      In CI, set these as environment variables. For `.netrc`-based auth with private GitHub:
      
      ```
      machine github.com login git password <personal-access-token>
      ```
      
      ---
      
      ## Workspace Mode
      
      Workspaces allow multiple modules to be developed together without `replace` directives.
      
      ```bash
      go work init ./app ./shared ./tools   # Creates go.work
      go work use ./new-module              # Add another module
      go work sync                          # Sync dependencies
      ```
      
      **go.work file:**
      
      ```
      go 1.22
      
      use (
          ./app
          ./shared
          ./tools
      )
      ```
      
      ### When Workspaces Help
      
      - Developing two modules simultaneously (e.g., a library and a consuming app)
      - Monorepo with multiple Go modules
      - Testing unreleased changes to a shared package before publishing
      
      ### When Workspaces Do Not Help
      
      - Single-module repos (no benefit)
      - Production builds — exclude `go.work` from Docker contexts with `.dockerignore`
      
      ```
      # .dockerignore
      go.work
      go.work.sum
      ```
      
      ---
      
      ## Build Tags
      
      Build tags control which files are included in a build. The modern syntax uses `//go:build`.
      
      ```go
      //go:build integration
      
      package mypackage
      ```
      
      ```go
      //go:build linux && amd64
      
      package mypackage
      ```
      
      ```go
      //go:build !windows
      
      package mypackage
      ```
      
      ### Common Tag Patterns
      
      ```go
      //go:build ignore          // Exclude from normal builds (e.g., generation scripts)
      
      //go:build integration     // Integration tests requiring real external services
      
      //go:build e2e             // End-to-end tests
      
      //go:build cgo             // Only build when CGO is enabled
      ```
      
      ### Run Builds with Tags
      
      ```bash
      go test -tags integration ./...
      go build -tags production ./cmd/server
      go vet -tags integration ./...
      ```
      
      ### Separate Integration Tests
      
      ```go
      //go:build integration
      
      package repository_test
      
      import (
          "testing"
          "os"
      )
      
      func TestUserRepository_Integration(t *testing.T) {
          dsn := os.Getenv("TEST_DATABASE_URL")
          if dsn == "" {
              t.Skip("TEST_DATABASE_URL not set")
          }
          // ... test against real database
      }
      ```
      
      ---
      
      ## Build Configuration
      
      ### Inject Version Information at Build Time
      
      ```go
      // internal/version/version.go
      package version
      
      var (
          Version   = "dev"
          GitCommit = "unknown"
          BuildDate = "unknown"
      )
      ```
      
      ```bash
      go build \
        -ldflags="-X github.com/myorg/myapp/internal/version.Version=1.2.3 \
                   -X github.com/myorg/myapp/internal/version.GitCommit=$(git rev-parse --short HEAD) \
                   -X github.com/myorg/myapp/internal/version.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
        ./cmd/server
      ```
      
      ### Build Static Binaries
      
      ```bash
      CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
        -ldflags="-s -w" \
        -trimpath \
        -o bin/server \
        ./cmd/server
      ```
      
      - `CGO_ENABLED=0`: Disable cgo, produce a statically linked binary
      - `-s -w`: Strip debug info and DWARF symbols (reduces binary size ~30%)
      - `-trimpath`: Remove local file paths from the binary (reproducible builds, avoids leaking local paths)
      
      ### Cross-Compile
      
      ```bash
      GOOS=windows GOARCH=amd64 go build ./cmd/server
      GOOS=darwin  GOARCH=arm64 go build ./cmd/server
      GOOS=linux   GOARCH=arm64 go build ./cmd/server
      ```
      
      ---
      
      ## Makefile and Justfile Patterns
      
      ### Makefile
      
      ```makefile
      BINARY     := bin/server
      VERSION    := $(shell git describe --tags --always --dirty)
      COMMIT     := $(shell git rev-parse --short HEAD)
      BUILD_DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
      LDFLAGS    := -X main.version=$(VERSION) -X main.commit=$(COMMIT)
      
      .PHONY: build test lint generate clean docker
      
      build:
      	CGO_ENABLED=0 go build -ldflags="$(LDFLAGS)" -trimpath -o $(BINARY) ./cmd/server
      
      test:
      	go test -race -coverprofile=coverage.out ./...
      	go tool cover -html=coverage.out -o coverage.html
      
      test-integration:
      	go test -race -tags integration ./...
      
      lint:
      	golangci-lint run ./...
      
      generate:
      	go generate ./...
      
      clean:
      	rm -rf bin/ coverage.out coverage.html
      
      docker:
      	docker build --build-arg VERSION=$(VERSION) -t myapp:$(VERSION) .
      
      tidy:
      	go mod tidy
      	go mod verify
      ```
      
      ### Justfile
      
      ```just
      version := `git describe --tags --always --dirty`
      commit  := `git rev-parse --short HEAD`
      
      build:
          CGO_ENABLED=0 go build \
              -ldflags="-X main.version={{version}} -X main.commit={{commit}}" \
              -trimpath -o bin/server ./cmd/server
      
      test:
          go test -race -coverprofile=coverage.out ./...
      
      test-integration:
          go test -race -tags integration ./...
      
      lint:
          golangci-lint run ./...
      
      generate:
          go generate ./...
      
      tidy:
          go mod tidy && go mod verify
      ```
      
      ---
      
      ## Linting
      
      ### Install and Run golangci-lint
      
      ```bash
      # Install (do not use go install — use the official installer)
      curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \
        | sh -s -- -b $(go env GOPATH)/bin v1.57.2
      
      golangci-lint run ./...
      golangci-lint run --fix ./...    # Auto-fix where possible
      ```
      
      ### Recommended .golangci.yml
      
      ```yaml
      linters:
        enable:
          - errcheck        # Check all error returns are handled
          - gosimple        # Simplification suggestions
          - govet           # go vet checks
          - ineffassign     # Detect unused variable assignments
          - staticcheck     # Comprehensive static analysis
          - unused          # Detect unused code
          - gofmt           # Enforce gofmt formatting
          - goimports       # Enforce import grouping
          - gocritic        # Opinionated style checks
          - misspell        # Catch common misspellings
          - prealloc        # Suggest slice pre-allocation
          - exhaustive      # Enforce exhaustive enum switches
          - noctx           # Detect HTTP requests without context
      
      linters-settings:
        errcheck:
          check-blank: true
        govet:
          enable-all: true
        gocritic:
          enabled-tags: [diagnostic, style, performance]
      
      issues:
        exclude-rules:
          - path: _test\.go
            linters: [errcheck]   # Relax error checking in tests
      ```
      
      ### Suppress Specific Warnings
      
      ```go
      //nolint:errcheck  // Intentionally ignoring close error on best-effort cleanup
      defer f.Close()
      
      //nolint:exhaustive  // Default case handles unrecognized values
      switch status {
      case Active:
          return "active"
      default:
          return "unknown"
      }
      ```
      
      ---
      
      ## Code Generation
      
      ### go generate
      
      Place `//go:generate` directives in the file where the generated output belongs conceptually.
      
      ```go
      // internal/domain/status.go
      //go:generate stringer -type=Status
      
      type Status int
      
      const (
          Active Status = iota
          Inactive
          Pending
      )
      ```
      
      ```go
      // internal/repository/mock_store.go (or a dedicated mocks/ dir)
      //go:generate mockgen -source=store.go -destination=mock_store.go -package=repository
      ```
      
      Run all generators:
      
      ```bash
      go generate ./...
      ```
      
      ### Embed Static Files
      
      ```go
      import "embed"
      
      //go:embed templates/*.html
      var templateFS embed.FS
      
      //go:embed migrations
      var migrationsFS embed.FS
      
      //go:embed static/app.js static/app.css
      var staticFiles embed.FS
      ```
      
      - Paths are relative to the file containing the directive
      - Supports glob patterns and directories
      - Embedded files are read-only at runtime
      
      ---
      
      ## Release
      
      ### goreleaser
      
      ```yaml
      # .goreleaser.yml
      project_name: myapp
      
      builds:
        - id: server
          main: ./cmd/server
          binary: server
          env:
            - CGO_ENABLED=0
          goos: [linux, darwin, windows]
          goarch: [amd64, arm64]
          ldflags:
            - -s -w
            - -X main.version={{.Version}}
            - -X main.commit={{.Commit}}
            - -X main.date={{.Date}}
          flags:
            - -trimpath
      
      archives:
        - format: tar.gz
          format_overrides:
            - goos: windows
              format: zip
      
      checksum:
        name_template: "checksums.txt"
      
      changelog:
        sort: asc
        filters:
          exclude: ['^docs:', '^test:', Merge pull request]
      ```
      
      ```bash
      # Dry run to verify configuration
      goreleaser release --snapshot --clean
      
      # Publish a real release (requires GITHUB_TOKEN)
      goreleaser release --clean
      ```
      
      ### Manual Cross-Compile Script
      
      ```bash
      #!/usr/bin/env bash
      set -euo pipefail
      
      VERSION=$(git describe --tags --always)
      PLATFORMS=("linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64" "windows/amd64")
      
      for platform in "${PLATFORMS[@]}"; do
          GOOS="${platform%/*}"
          GOARCH="${platform#*/}"
          output="dist/server_${GOOS}_${GOARCH}"
          [[ "$GOOS" == "windows" ]] && output="${output}.exe"
      
          echo "Building $output"
          CGO_ENABLED=0 GOOS="$GOOS" GOARCH="$GOARCH" go build \
              -ldflags="-s -w -X main.version=${VERSION}" \
              -trimpath -o "$output" ./cmd/server
      done
      ```
      
    • testing.md 16.7 KB
      # Go Testing Reference
      
      ## Table of Contents
      
      1. [Table-Driven Tests](#1-table-driven-tests)
      2. [Test Helpers](#2-test-helpers)
      3. [Mocking with Interfaces](#3-mocking-with-interfaces)
      4. [testify](#4-testify)
      5. [httptest](#5-httptest)
      6. [Benchmarks](#6-benchmarks)
      7. [Fuzz Testing](#7-fuzz-testing)
      8. [Integration Tests](#8-integration-tests)
      9. [Golden Files](#9-golden-files)
      10. [Test Fixtures and TestMain](#10-test-fixtures-and-testmain)
      11. [Race Detection](#11-race-detection)
      12. [Coverage](#12-coverage)
      
      ---
      
      ## 1. Table-Driven Tests
      
      Write tests as a slice of structs. Name the slice `tests` and each element `tt`. Run each with `t.Run`.
      
      ```go
      func TestDivide(t *testing.T) {
          t.Parallel()
      
          tests := []struct {
              name      string
              dividend  float64
              divisor   float64
              want      float64
              wantErr   bool
          }{
              {name: "positive", dividend: 10, divisor: 2, want: 5},
              {name: "negative divisor", dividend: 10, divisor: -2, want: -5},
              {name: "fractional result", dividend: 7, divisor: 2, want: 3.5},
              {name: "zero divisor", dividend: 10, divisor: 0, wantErr: true},
          }
      
          for _, tt := range tests {
              t.Run(tt.name, func(t *testing.T) {
                  t.Parallel() // run subtests in parallel when safe
      
                  got, err := Divide(tt.dividend, tt.divisor)
      
                  if tt.wantErr {
                      if err == nil {
                          t.Fatal("expected error, got nil")
                      }
                      return
                  }
      
                  if err != nil {
                      t.Fatalf("unexpected error: %v", err)
                  }
                  if got != tt.want {
                      t.Errorf("Divide(%v, %v) = %v, want %v",
                          tt.dividend, tt.divisor, got, tt.want)
                  }
              })
          }
      }
      ```
      
      Use `t.Fatal` when further execution is meaningless. Use `t.Error` to accumulate multiple failures. Capture loop variables before `t.Parallel()` in Go versions before 1.22 (Go 1.22+ fixes loop variable capture automatically).
      
      ---
      
      ## 2. Test Helpers
      
      ### t.Helper
      
      Mark helper functions with `t.Helper()` so failures report the caller's line, not the helper's.
      
      ```go
      func assertNoError(t *testing.T, err error) {
          t.Helper()
          if err != nil {
              t.Fatalf("unexpected error: %v", err)
          }
      }
      
      func assertEqual[T comparable](t *testing.T, got, want T) {
          t.Helper()
          if got != want {
              t.Errorf("got %v, want %v", got, want)
          }
      }
      ```
      
      ### t.Cleanup
      
      Register cleanup functions that run even if the test panics or calls `t.Fatal`.
      
      ```go
      func newTestDB(t *testing.T) *sql.DB {
          t.Helper()
      
          db, err := sql.Open("sqlite3", ":memory:")
          if err != nil {
              t.Fatalf("opening db: %v", err)
          }
      
          t.Cleanup(func() {
              if err := db.Close(); err != nil {
                  t.Errorf("closing db: %v", err)
              }
          })
      
          return db
      }
      ```
      
      ### t.TempDir
      
      Use `t.TempDir()` instead of `os.MkdirTemp`. It is automatically removed after the test.
      
      ```go
      func TestWriteFile(t *testing.T) {
          dir := t.TempDir() // cleaned up automatically
      
          path := filepath.Join(dir, "output.txt")
          err := WriteFile(path, []byte("hello"))
          if err != nil {
              t.Fatal(err)
          }
      
          got, err := os.ReadFile(path)
          if err != nil {
              t.Fatal(err)
          }
          if string(got) != "hello" {
              t.Errorf("got %q, want %q", got, "hello")
          }
      }
      ```
      
      ### testdata Directory
      
      Place static input files in `testdata/`. The Go tool ignores this directory for builds. Reference files relative to the package root using `filepath.Join("testdata", "input.json")`.
      
      ```go
      func TestParseConfig(t *testing.T) {
          data, err := os.ReadFile(filepath.Join("testdata", "config.json"))
          if err != nil {
              t.Fatal(err)
          }
      
          cfg, err := ParseConfig(data)
          if err != nil {
              t.Fatalf("ParseConfig: %v", err)
          }
          if cfg.Port != 8080 {
              t.Errorf("got port %d, want 8080", cfg.Port)
          }
      }
      ```
      
      ---
      
      ## 3. Mocking with Interfaces
      
      Define narrow interfaces at the point of use, not in the package that implements them.
      
      ```go
      // Define the interface (in the consumer's package)
      type UserStore interface {
          GetUser(ctx context.Context, id int64) (*User, error)
          SaveUser(ctx context.Context, u *User) error
      }
      
      // Hand-rolled mock (no external dependencies)
      type mockUserStore struct {
          getUser  func(ctx context.Context, id int64) (*User, error)
          saveUser func(ctx context.Context, u *User) error
          calls    []string
      }
      
      func (m *mockUserStore) GetUser(ctx context.Context, id int64) (*User, error) {
          m.calls = append(m.calls, "GetUser")
          if m.getUser != nil {
              return m.getUser(ctx, id)
          }
          return nil, nil
      }
      
      func (m *mockUserStore) SaveUser(ctx context.Context, u *User) error {
          m.calls = append(m.calls, "SaveUser")
          if m.saveUser != nil {
              return m.saveUser(ctx, u)
          }
          return nil
      }
      
      // Test using the mock
      func TestUserService_Promote(t *testing.T) {
          store := &mockUserStore{
              getUser: func(_ context.Context, id int64) (*User, error) {
                  return &User{ID: id, Role: "member"}, nil
              },
              saveUser: func(_ context.Context, u *User) error {
                  if u.Role != "admin" {
                      return fmt.Errorf("expected role admin, got %s", u.Role)
                  }
                  return nil
              },
          }
      
          svc := NewUserService(store)
          err := svc.Promote(context.Background(), 42)
          if err != nil {
              t.Fatalf("Promote: %v", err)
          }
          if len(store.calls) != 2 {
              t.Errorf("expected 2 calls, got %d: %v", len(store.calls), store.calls)
          }
      }
      ```
      
      ---
      
      ## 4. testify
      
      Install: `go get github.com/stretchr/testify`.
      
      ### assert vs require
      
      `assert` logs failure and continues. `require` stops the test immediately (calls `t.FailNow`).
      
      ```go
      import (
          "testing"
          "github.com/stretchr/testify/assert"
          "github.com/stretchr/testify/require"
      )
      
      func TestUserCreation(t *testing.T) {
          user, err := NewUser("alice@example.com")
      
          require.NoError(t, err)            // stop if error
          require.NotNil(t, user)            // stop if nil
      
          assert.Equal(t, "alice@example.com", user.Email)
          assert.Empty(t, user.PasswordHash) // multiple checks continue on failure
          assert.WithinDuration(t, time.Now(), user.CreatedAt, time.Second)
      }
      ```
      
      ### testify/suite
      
      Group related tests with shared setup/teardown.
      
      ```go
      import "github.com/stretchr/testify/suite"
      
      type UserSuite struct {
          suite.Suite
          db  *sql.DB
          svc *UserService
      }
      
      func (s *UserSuite) SetupSuite() {
          db, err := sql.Open("sqlite3", ":memory:")
          s.Require().NoError(err)
          s.db = db
          s.svc = NewUserService(db)
      }
      
      func (s *UserSuite) TearDownSuite() {
          s.db.Close()
      }
      
      func (s *UserSuite) SetupTest() {
          _, err := s.db.Exec("DELETE FROM users")
          s.Require().NoError(err)
      }
      
      func (s *UserSuite) TestCreate() {
          u, err := s.svc.Create(context.Background(), "bob@example.com")
          s.Require().NoError(err)
          s.Equal("bob@example.com", u.Email)
      }
      
      func TestUserSuite(t *testing.T) {
          suite.Run(t, new(UserSuite))
      }
      ```
      
      ### testify/mock
      
      Use `mock.Mock` for dynamic expectations with call counting.
      
      ```go
      import "github.com/stretchr/testify/mock"
      
      type MockStore struct {
          mock.Mock
      }
      
      func (m *MockStore) GetUser(ctx context.Context, id int64) (*User, error) {
          args := m.Called(ctx, id)
          return args.Get(0).(*User), args.Error(1)
      }
      
      func TestWithMock(t *testing.T) {
          store := new(MockStore)
          store.On("GetUser", mock.Anything, int64(1)).
              Return(&User{ID: 1, Name: "Alice"}, nil)
      
          svc := NewUserService(store)
          user, err := svc.GetUser(context.Background(), 1)
      
          require.NoError(t, err)
          assert.Equal(t, "Alice", user.Name)
          store.AssertExpectations(t)
      }
      ```
      
      ---
      
      ## 5. httptest
      
      ### Test HTTP Handlers Directly
      
      ```go
      import "net/http/httptest"
      
      func TestGetUserHandler(t *testing.T) {
          store := &mockUserStore{
              getUser: func(_ context.Context, id int64) (*User, error) {
                  return &User{ID: id, Name: "Alice"}, nil
              },
          }
          h := NewHandler(store)
      
          req := httptest.NewRequest(http.MethodGet, "/users/1", nil)
          w := httptest.NewRecorder()
      
          h.ServeHTTP(w, req)
      
          resp := w.Result()
          defer resp.Body.Close()
      
          if resp.StatusCode != http.StatusOK {
              t.Fatalf("status %d, want 200", resp.StatusCode)
          }
      
          var got User
          if err := json.NewDecoder(resp.Body).Decode(&got); err != nil {
              t.Fatalf("decoding response: %v", err)
          }
          if got.Name != "Alice" {
              t.Errorf("name %q, want Alice", got.Name)
          }
      }
      ```
      
      ### Test HTTP Clients Against a Real Server
      
      ```go
      func TestAPIClient(t *testing.T) {
          srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
              if r.URL.Path != "/v1/users/42" {
                  t.Errorf("unexpected path: %s", r.URL.Path)
              }
              w.Header().Set("Content-Type", "application/json")
              fmt.Fprintln(w, `{"id":42,"name":"Bob"}`)
          }))
          defer srv.Close()
      
          client := NewAPIClient(srv.URL)
          user, err := client.GetUser(context.Background(), 42)
      
          if err != nil {
              t.Fatalf("GetUser: %v", err)
          }
          if user.Name != "Bob" {
              t.Errorf("name %q, want Bob", user.Name)
          }
      }
      
      // TLS variant
      func TestAPIClientTLS(t *testing.T) {
          srv := httptest.NewTLSServer(myHandler)
          defer srv.Close()
      
          client := srv.Client() // pre-configured to trust the test certificate
          resp, err := client.Get(srv.URL + "/health")
          // ...
      }
      ```
      
      ---
      
      ## 6. Benchmarks
      
      Functions named `BenchmarkXxx` receive `*testing.B`. Run with `go test -bench=. -benchmem`.
      
      ```go
      func BenchmarkEncode(b *testing.B) {
          user := &User{ID: 1, Name: "Alice", Email: "alice@example.com"}
      
          b.ReportAllocs()  // show allocations per op
          b.ResetTimer()    // exclude setup time
      
          for b.Loop() {    // Go 1.24+; use i := 0; i < b.N; i++ for older versions
              if _, err := json.Marshal(user); err != nil {
                  b.Fatal(err)
              }
          }
      }
      
      // Sub-benchmarks compare implementations
      func BenchmarkEncoding(b *testing.B) {
          user := &User{ID: 1, Name: "Alice", Email: "alice@example.com"}
      
          b.Run("json/stdlib", func(b *testing.B) {
              for b.Loop() {
                  json.Marshal(user)
              }
          })
      
          b.Run("json/sonic", func(b *testing.B) {
              for b.Loop() {
                  sonic.Marshal(user)
              }
          })
      }
      
      // Parallel benchmark
      func BenchmarkEncodeParallel(b *testing.B) {
          user := &User{ID: 1, Name: "Alice"}
      
          b.RunParallel(func(pb *testing.PB) {
              for pb.Next() {
                  json.Marshal(user)
              }
          })
      }
      ```
      
      Run and compare: `go test -bench=BenchmarkEncoding -benchmem -count=5 | tee new.txt && benchstat old.txt new.txt`.
      
      ---
      
      ## 7. Fuzz Testing
      
      Fuzz tests find inputs that crash your code. Run normally as unit tests; enable fuzzing with `-fuzz`.
      
      ```go
      func FuzzParseURL(f *testing.F) {
          // Seed the corpus with known-good inputs
          f.Add("https://example.com/path?q=1")
          f.Add("http://localhost:8080")
          f.Add("")
          f.Add("not-a-url")
      
          f.Fuzz(func(t *testing.T, raw string) {
              // Must not panic
              u, err := ParseURL(raw)
              if err != nil {
                  return // errors are acceptable
              }
      
              // Round-trip property: re-parsing the output must succeed
              reparsed, err := ParseURL(u.String())
              if err != nil {
                  t.Errorf("round-trip failed for %q: %v", u.String(), err)
              }
              if reparsed.String() != u.String() {
                  t.Errorf("round-trip mismatch: %q != %q", reparsed.String(), u.String())
              }
          })
      }
      ```
      
      Run fuzzing: `go test -fuzz=FuzzParseURL -fuzztime=30s`. Failing inputs are saved to `testdata/fuzz/FuzzParseURL/`. Reproduce: `go test -run=FuzzParseURL/testdata/fuzz/FuzzParseURL/<id>`.
      
      ---
      
      ## 8. Integration Tests
      
      ### Build Tags
      
      Guard integration tests with a build tag so `go test ./...` skips them by default.
      
      ```go
      //go:build integration
      
      package store_test
      
      import (
          "testing"
          // ...
      )
      
      func TestPostgresUserStore(t *testing.T) {
          dsn := os.Getenv("TEST_DSN")
          if dsn == "" {
              t.Skip("TEST_DSN not set")
          }
          // ...
      }
      ```
      
      Run: `go test -tags integration ./...`
      
      ### testcontainers-go
      
      Spin up real databases in Docker for integration tests.
      
      ```go
      //go:build integration
      
      func TestWithPostgres(t *testing.T) {
          ctx := context.Background()
      
          container, err := postgres.RunContainer(ctx,
              testcontainers.WithImage("postgres:16"),
              postgres.WithDatabase("testdb"),
              postgres.WithUsername("test"),
              postgres.WithPassword("test"),
              testcontainers.WithWaitStrategy(
                  wait.ForLog("database system is ready to accept connections").
                      WithOccurrence(2)),
          )
          if err != nil {
              t.Fatalf("starting postgres: %v", err)
          }
          t.Cleanup(func() { container.Terminate(ctx) })
      
          dsn, err := container.ConnectionString(ctx, "sslmode=disable")
          if err != nil {
              t.Fatal(err)
          }
      
          db, err := sql.Open("postgres", dsn)
          if err != nil {
              t.Fatal(err)
          }
          t.Cleanup(func() { db.Close() })
      
          // Run migrations, then test
          runMigrations(t, db)
          store := NewPostgresStore(db)
          // ... test store methods
      }
      ```
      
      ---
      
      ## 9. Golden Files
      
      Golden files store expected output. Re-generate them with `-update`.
      
      ```go
      var update = flag.Bool("update", false, "update golden files")
      
      func TestRenderMarkdown(t *testing.T) {
          input, err := os.ReadFile(filepath.Join("testdata", "input.md"))
          if err != nil {
              t.Fatal(err)
          }
      
          got := RenderMarkdown(input)
      
          golden := filepath.Join("testdata", "golden", "output.html")
      
          if *update {
              err := os.WriteFile(golden, got, 0644)
              if err != nil {
                  t.Fatal(err)
              }
              return
          }
      
          want, err := os.ReadFile(golden)
          if err != nil {
              t.Fatal(err)
          }
      
          if !bytes.Equal(got, want) {
              t.Errorf("output mismatch (-want +got):\n%s",
                  cmp.Diff(string(want), string(got)))
          }
      }
      ```
      
      Run `go test -run=TestRenderMarkdown -update` to regenerate, then commit the golden files.
      
      ---
      
      ## 10. Test Fixtures and TestMain
      
      ### TestMain for Global Setup
      
      ```go
      func TestMain(m *testing.M) {
          // Setup: runs once before any test
          db, err := setupTestDatabase()
          if err != nil {
              fmt.Fprintf(os.Stderr, "setup: %v\n", err)
              os.Exit(1)
          }
          globalDB = db
      
          // Run tests
          code := m.Run()
      
          // Teardown: runs once after all tests
          db.Close()
          os.Exit(code)
      }
      ```
      
      ### Per-Test Setup with t.Cleanup
      
      Prefer `t.Cleanup` over `defer` in test helpers; it composes across multiple helpers cleanly.
      
      ```go
      func prepareUser(t *testing.T, db *sql.DB, email string) *User {
          t.Helper()
      
          u, err := db.CreateUser(context.Background(), email)
          if err != nil {
              t.Fatalf("creating user: %v", err)
          }
      
          t.Cleanup(func() {
              if err := db.DeleteUser(context.Background(), u.ID); err != nil {
                  t.Logf("cleanup: deleting user %d: %v", u.ID, err)
              }
          })
      
          return u
      }
      ```
      
      ---
      
      ## 11. Race Detection
      
      Enable with `go test -race ./...`. The race detector adds ~5-10x overhead; use it in CI.
      
      ### Common Race: Shared State in Goroutines
      
      ```go
      // RACE: multiple goroutines write to results without synchronization
      func badCollect(items []Item) []Result {
          results := make([]Result, 0, len(items))
          var wg sync.WaitGroup
          for _, item := range items {
              wg.Add(1)
              go func(it Item) {
                  defer wg.Done()
                  results = append(results, process(it)) // DATA RACE
              }(item)
          }
          wg.Wait()
          return results
      }
      
      // FIXED: preallocate by index
      func goodCollect(items []Item) []Result {
          results := make([]Result, len(items))
          var wg sync.WaitGroup
          for i, item := range items {
              wg.Add(1)
              go func(i int, it Item) {
                  defer wg.Done()
                  results[i] = process(it) // safe: each goroutine owns its index
              }(i, item)
          }
          wg.Wait()
          return results
      }
      ```
      
      ### Common Race: Closing Over Loop Variables (pre-Go 1.22)
      
      ```go
      // RACE in Go < 1.22
      for _, url := range urls {
          go func() {
              fetch(url) // captures loop variable by reference
          }()
      }
      
      // FIXED
      for _, url := range urls {
          url := url // shadow with local copy
          go func() {
              fetch(url)
          }()
      }
      ```
      
      ---
      
      ## 12. Coverage
      
      ```bash
      # Generate coverage profile
      go test -coverprofile=coverage.out ./...
      
      # View summary by package
      go tool cover -func=coverage.out
      
      # Open interactive HTML report
      go tool cover -html=coverage.out
      
      # Enforce a minimum threshold in CI
      go test -coverprofile=coverage.out ./...
      go tool cover -func=coverage.out | awk '/^total:/ {pct=$3+0; if (pct < 80) {print "coverage "$pct"% below 80%"; exit 1}}'
      ```
      
      Target 80% coverage for business logic. Avoid chasing 100%: generated code, main functions, and deliberate error paths that only trigger under hardware failure are not worth testing directly.
      
  • scripts
    • .gitkeep 0 B · in bundle
  • SKILL.md 9.4 KB
    ---
    name: go-ops
    description: "Go development patterns, concurrency, error handling, testing, and project structure. Use for: golang, go, goroutine, channel, context, errgroup, go test, go mod, go build, interface, generics, table-driven tests, worker pool, sync.Mutex, sync.WaitGroup, pprof, go vet, golangci-lint, go workspace, functional options, middleware, http handler."
    license: MIT
    allowed-tools: "Read Write Bash"
    metadata:
      author: claude-mods
      related-skills: docker-ops, ci-cd-ops, api-design-ops, testing-ops
    ---
    
    # Go Operations
    
    Comprehensive Go skill covering idiomatic patterns, concurrency, and production practices.
    
    ## Module Quick Start
    
    ```bash
    # New module
    go mod init github.com/user/project
    
    # Add dependency
    go get github.com/lib/pq@latest
    
    # Tidy (remove unused, add missing)
    go mod tidy
    
    # Vendor dependencies
    go mod vendor
    
    # Workspace (multi-module)
    go work init ./api ./shared
    go work use ./cli
    ```
    
    ## Error Handling Decision Tree
    
    ```
    What kind of error?
    │
    ├─ Known, expected condition (e.g. "not found")
    │  └─ Sentinel error: var ErrNotFound = errors.New("not found")
    │     └─ Caller checks: errors.Is(err, ErrNotFound)
    │
    ├─ Need to carry structured data (status code, field name)
    │  └─ Custom error type: type ValidationError struct { Field, Message string }
    │     └─ Implement Error() string
    │     └─ Caller checks: errors.As(err, &validErr)
    │
    ├─ Adding context to an existing error
    │  └─ Wrap: fmt.Errorf("load config: %w", err)
    │     └─ Preserves original for Is/As checks
    │
    ├─ Truly unrecoverable (corrupted state, programmer bug)
    │  └─ panic("invariant violated: ...")
    │     └─ Almost never in library code
    │
    └─ Multiple errors from concurrent work
       └─ errors.Join(err1, err2) or multierr package
    ```
    
    ### Error Wrapping Convention
    
    ```go
    // Add context at each layer, don't repeat the function name
    func LoadUser(id int) (*User, error) {
        row, err := db.Query("SELECT ...", id)
        if err != nil {
            return nil, fmt.Errorf("load user %d: %w", id, err)
        }
        // ...
    }
    ```
    
    ## Concurrency Decision Tree
    
    ```
    What's the concurrency pattern?
    │
    ├─ Run N independent tasks, collect results
    │  └─ errgroup.Group (cancels on first error)
    │
    ├─ Fire-and-forget background work
    │  └─ go func() with context for cancellation
    │     └─ ALWAYS handle the error or log it
    │
    ├─ Producer/consumer pipeline
    │  └─ Channels (buffered for throughput)
    │     └─ Close channel when producer is done
    │
    ├─ Rate-limited concurrent work
    │  └─ Semaphore: make(chan struct{}, maxConcurrency)
    │
    ├─ Shared mutable state
    │  └─ sync.Mutex or sync.RWMutex
    │     └─ Prefer channels if the state is simple
    │
    ├─ One-time initialization
    │  └─ sync.Once
    │
    └─ Wait for N goroutines to finish (no error collection)
       └─ sync.WaitGroup
    ```
    
    ### errgroup Quick Start
    
    ```go
    import "golang.org/x/sync/errgroup"
    
    g, ctx := errgroup.WithContext(ctx)
    g.SetLimit(10) // max 10 concurrent goroutines
    
    for _, url := range urls {
        g.Go(func() error {
            return fetch(ctx, url)
        })
    }
    
    if err := g.Wait(); err != nil {
        return fmt.Errorf("fetch urls: %w", err)
    }
    ```
    
    **Deep dive**: Load `./references/concurrency.md` for worker pools, fan-out/fan-in, pipeline patterns, context best practices.
    
    ## Interface Design
    
    ```
    Accept interfaces, return structs.
    ```
    
    ```go
    // Good: function accepts interface
    func Process(r io.Reader) error { ... }
    
    // Good: return concrete type
    func NewServer(cfg Config) *Server { ... }
    
    // Bad: returning interface (hides implementation, prevents extension)
    func NewServer(cfg Config) ServerInterface { ... }
    ```
    
    ### Common Stdlib Interfaces
    
    | Interface | Methods | Use For |
    |-----------|---------|---------|
    | `io.Reader` | `Read([]byte) (int, error)` | Any byte source |
    | `io.Writer` | `Write([]byte) (int, error)` | Any byte sink |
    | `io.Closer` | `Close() error` | Resource cleanup |
    | `fmt.Stringer` | `String() string` | String representation |
    | `error` | `Error() string` | Error values |
    | `sort.Interface` | `Len, Less, Swap` | Custom sorting |
    | `http.Handler` | `ServeHTTP(w, r)` | HTTP handlers |
    | `encoding.BinaryMarshaler` | `MarshalBinary() ([]byte, error)` | Binary encoding |
    
    ### Functional Options Pattern
    
    ```go
    type Option func(*Server)
    
    func WithPort(port int) Option {
        return func(s *Server) { s.port = port }
    }
    
    func WithTimeout(d time.Duration) Option {
        return func(s *Server) { s.timeout = d }
    }
    
    func NewServer(opts ...Option) *Server {
        s := &Server{port: 8080, timeout: 30 * time.Second} // defaults
        for _, opt := range opts {
            opt(s)
        }
        return s
    }
    
    // Usage
    srv := NewServer(WithPort(9090), WithTimeout(5*time.Second))
    ```
    
    **Deep dive**: Load `./references/interfaces-generics.md` for generics, type constraints, embedding, type assertions.
    
    ## Testing Quick Reference
    
    ```go
    // Table-driven test
    func TestAdd(t *testing.T) {
        tests := []struct {
            name     string
            a, b     int
            expected int
        }{
            {"positive", 1, 2, 3},
            {"zero", 0, 0, 0},
            {"negative", -1, -2, -3},
        }
        for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
                got := Add(tt.a, tt.b)
                if got != tt.expected {
                    t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.expected)
                }
            })
        }
    }
    ```
    
    ```bash
    # Run tests
    go test ./...
    
    # With coverage
    go test -cover -coverprofile=coverage.out ./...
    go tool cover -html=coverage.out
    
    # Run specific test
    go test -run TestAdd ./pkg/math/
    
    # Benchmarks
    go test -bench=. -benchmem ./...
    
    # Race detector
    go test -race ./...
    
    # Fuzz testing
    go test -fuzz=FuzzParse ./...
    ```
    
    **Deep dive**: Load `./references/testing.md` for mocking with interfaces, httptest, testcontainers, golden files.
    
    ## Common Gotchas
    
    | Gotcha | Why | Fix |
    |--------|-----|-----|
    | Nil slice vs empty slice | `var s []int` is nil, `s := []int{}` is empty. `json.Marshal` gives `null` vs `[]` | Use `make([]int, 0)` or `[]int{}` if JSON matters |
    | Goroutine leak | Goroutine blocked on channel with no reader/writer | Use `context.WithCancel`, always provide exit path |
    | Defer in loop | Deferred calls don't run until function returns | Wrap loop body in a closure or use explicit cleanup |
    | Interface nil pitfall | `(*MyType)(nil)` assigned to `error` interface is not `== nil` | Return `nil` explicitly, not a nil typed pointer |
    | Range variable capture | Loop var reused (pre-Go 1.22) | Use `go func(v T) { ... }(v)` or upgrade to Go 1.22+ |
    | String concatenation in loop | O(n^2) allocation | Use `strings.Builder` |
    | `sync.WaitGroup` Add after Go | Race condition | Call `wg.Add(1)` before `go func()` |
    | Unbuffered channel deadlock | Send/receive must happen concurrently | Use buffered channel or separate goroutines |
    | `map` not safe for concurrent use | Race condition, may crash | Use `sync.Mutex` or `sync.Map` |
    
    ## Project Structure
    
    ```
    project/
    ├── cmd/
    │   ├── api/main.go           # Entry points
    │   └── worker/main.go
    ├── internal/                  # Private packages
    │   ├── handler/
    │   ├── service/
    │   └── repository/
    ├── pkg/                       # Public packages (optional)
    ├── go.mod
    ├── go.sum
    ├── Makefile                   # or justfile
    └── .golangci.yml
    ```
    
    **Deep dive**: Load `./references/project-structure.md` for workspace mode, build tags, ldflags, linting config.
    
    ## Performance Quick Reference
    
    ```bash
    # CPU profile
    go test -cpuprofile=cpu.prof -bench=. ./...
    go tool pprof cpu.prof
    
    # Memory profile
    go test -memprofile=mem.prof -bench=. ./...
    go tool pprof -alloc_space mem.prof
    
    # Trace
    go test -trace=trace.out ./...
    go tool trace trace.out
    
    # Escape analysis
    go build -gcflags='-m' ./...
    ```
    
    | Optimization | When | Pattern |
    |-------------|------|---------|
    | Pre-allocate slices | Known size | `make([]T, 0, n)` |
    | `strings.Builder` | String concatenation | `var b strings.Builder` |
    | `sync.Pool` | Frequent alloc/free of same type | `pool.Get()` / `pool.Put()` |
    | Struct field alignment | Memory-sensitive | Group fields by size (largest first) |
    | Buffer reuse | I/O-heavy | `bufio.NewReaderSize(r, 64*1024)` |
    
    **Deep dive**: Load `./references/performance.md` for pprof walkthrough, benchmarking patterns, escape analysis.
    
    ## Reference Files
    
    Load these for deep-dive topics. Each is self-contained.
    
    | Reference | When to Load |
    |-----------|-------------|
    | `./references/concurrency.md` | Goroutines, channels, context, sync primitives, worker pools, pipelines |
    | `./references/error-handling.md` | Error wrapping, sentinel errors, custom types, multi-error, panic/recover |
    | `./references/testing.md` | Table tests, mocking, httptest, benchmarks, fuzz, testcontainers, golden files |
    | `./references/interfaces-generics.md` | Interface design, embedding, type assertions, generics, type constraints |
    | `./references/project-structure.md` | Standard layout, go.mod, workspaces, build tags, ldflags, golangci-lint |
    | `./references/performance.md` | pprof, trace, benchmarks, escape analysis, sync.Pool, struct alignment |
    | `./references/expert-insights.md` | HTTP server (Go 1.22 routing), graceful shutdown, http.Client tuning, JSON helpers |
    
    ## See Also
    
    - `docker-ops` - Multi-stage builds for Go binaries (scratch/distroless)
    - `ci-cd-ops` - Go CI pipelines, caching go modules, goreleaser
    - `testing-ops` - Cross-language testing strategies
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related