go-defensive

📁 cxuu/golang-skills 📅 Jan 27, 2026
63
总安装量
17
周安装量
#6466
全站排名
安装命令
npx skills add https://github.com/cxuu/golang-skills --skill go-defensive

Agent 安装分布

claude-code 12
github-copilot 12
cursor 11
gemini-cli 10
codex 10
opencode 9

Skill 文档

Go Defensive Programming Patterns

Verify Interface Compliance

Source: Uber Go Style Guide

Verify interface compliance at compile time using zero-value assertions.

Bad

type Handler struct{}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  // ...
}

Good

type Handler struct{}

var _ http.Handler = (*Handler)(nil)

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  // ...
}

Use nil for pointer types, slices, maps; empty struct {} for value receivers.

Copy Slices and Maps at Boundaries

Source: Uber Go Style Guide

Slices and maps contain pointers. Copy at API boundaries to prevent unintended modifications.

Receiving

Bad

func (d *Driver) SetTrips(trips []Trip) {
  d.trips = trips  // caller can still modify d.trips
}

Good

func (d *Driver) SetTrips(trips []Trip) {
  d.trips = make([]Trip, len(trips))
  copy(d.trips, trips)
}

Returning

Bad

func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()
  return s.counters  // exposes internal state!
}

Good

func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()
  result := make(map[string]int, len(s.counters))
  for k, v := range s.counters {
    result[k] = v
  }
  return result
}

Defer to Clean Up

Source: Uber Go Style Guide, Effective Go

Use defer to clean up resources (files, locks). Avoids missed cleanup on multiple returns.

Bad

p.Lock()
if p.count < 10 {
  p.Unlock()
  return p.count
}
p.count++
newCount := p.count
p.Unlock()
return newCount  // easy to miss unlocks

Good

p.Lock()
defer p.Unlock()

if p.count < 10 {
  return p.count
}
p.count++
return p.count

Defer overhead is negligible. Only avoid in nanosecond-critical paths.

Defer for File Operations

Place defer f.Close() immediately after opening a file for clarity:

func Contents(filename string) (string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer f.Close()  // Close sits near Open - much clearer

    var result []byte
    buf := make([]byte, 100)
    for {
        n, err := f.Read(buf[0:])
        result = append(result, buf[0:n]...)
        if err != nil {
            if err == io.EOF {
                break
            }
            return "", err  // f will be closed
        }
    }
    return string(result), nil  // f will be closed
}

Defer Argument Evaluation

Arguments to deferred functions are evaluated when defer executes, not when the deferred function runs:

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}
// Prints: 4 3 2 1 0 (LIFO order, values captured at defer time)

Defer LIFO Order

Multiple defers execute in Last-In-First-Out order:

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}

func un(s string) {
    fmt.Println("leaving:", s)
}

func a() {
    defer un(trace("a"))  // trace() runs now, un() runs at return
    fmt.Println("in a")
}
// Output: entering: a, in a, leaving: a

Start Enums at One

Source: Uber Go Style Guide

Start enums at non-zero to distinguish uninitialized from valid values.

Bad

const (
  Add Operation = iota  // Add=0, zero value looks valid
  Subtract
  Multiply
)

Good

const (
  Add Operation = iota + 1  // Add=1, zero value = uninitialized
  Subtract
  Multiply
)

Exception: When zero is the sensible default (e.g., LogToStdout = iota).

Use time.Time and time.Duration

Source: Uber Go Style Guide

Always use the time package. Avoid raw int for time values.

Instants

Bad

func isActive(now, start, stop int) bool {
  return start <= now && now < stop
}

Good

func isActive(now, start, stop time.Time) bool {
  return (start.Before(now) || start.Equal(now)) && now.Before(stop)
}

Durations

Bad

func poll(delay int) {
  time.Sleep(time.Duration(delay) * time.Millisecond)
}
poll(10)  // seconds? milliseconds?

Good

func poll(delay time.Duration) {
  time.Sleep(delay)
}
poll(10 * time.Second)

JSON Fields

When time.Duration isn’t possible, include unit in field name:

Bad

type Config struct {
  Interval int `json:"interval"`
}

Good

type Config struct {
  IntervalMillis int `json:"intervalMillis"`
}

Avoid Mutable Globals

Source: Uber Go Style Guide

Use dependency injection instead of mutable globals.

Bad

var _timeNow = time.Now

func sign(msg string) string {
  now := _timeNow()
  return signWithTime(msg, now)
}

// Test requires save/restore of global
func TestSign(t *testing.T) {
  oldTimeNow := _timeNow
  _timeNow = func() time.Time { return someFixedTime }
  defer func() { _timeNow = oldTimeNow }()
  assert.Equal(t, want, sign(give))
}

Good

type signer struct {
  now func() time.Time
}

func newSigner() *signer {
  return &signer{now: time.Now}
}

func (s *signer) Sign(msg string) string {
  now := s.now()
  return signWithTime(msg, now)
}

// Test injects dependency cleanly
func TestSigner(t *testing.T) {
  s := newSigner()
  s.now = func() time.Time { return someFixedTime }
  assert.Equal(t, want, s.Sign(give))
}

Avoid Embedding Types in Public Structs

Source: Uber Go Style Guide

Embedded types leak implementation details and inhibit type evolution.

Bad

type ConcreteList struct {
  *AbstractList
}

Good

type ConcreteList struct {
  list *AbstractList
}

func (l *ConcreteList) Add(e Entity) {
  l.list.Add(e)
}

func (l *ConcreteList) Remove(e Entity) {
  l.list.Remove(e)
}

Embedding problems:

  • Adding methods to embedded interface is a breaking change
  • Removing methods from embedded struct is a breaking change
  • Replacing the embedded type is a breaking change

Use Field Tags in Marshaled Structs

Source: Uber Go Style Guide

Always use explicit field tags for JSON, YAML, etc.

Bad

type Stock struct {
  Price int
  Name  string
}

Good

type Stock struct {
  Price int    `json:"price"`
  Name  string `json:"name"`
  // Safe to rename Name to Symbol
}

Tags make the serialization contract explicit and safe to refactor.


Crypto Rand

Source: Go Wiki CodeReviewComments (Normative)

Do not use math/rand or math/rand/v2 to generate keys, even throwaway ones. This is a security concern.

Unseeded or time-seeded random generators have predictable output:

  • Time.Nanoseconds() provides only a few bits of entropy
  • Keys generated this way can be guessed by attackers

Use crypto/rand instead:

import (
	"crypto/rand"
)

func Key() string {
	return rand.Text()
}

For text output:

  • Use crypto/rand.Text directly (preferred)
  • Or encode random bytes with encoding/hex or encoding/base64

Panic and Recover

Source: Effective Go

Use panic only for truly unrecoverable situations. Library functions should avoid panic—if the problem can be worked around, let things continue rather than taking down the whole program.

Use recover to regain control of a panicking goroutine (only works inside deferred functions):

func safelyDo(work *Work) {
    defer func() {
        if err := recover(); err != nil {
            log.Println("work failed:", err)
        }
    }()
    do(work)
}

Key rules:

  • Never expose panics across package boundaries—always convert to errors
  • Acceptable to panic in init() if a library truly cannot set itself up
  • Use recover to isolate panics in server goroutine handlers

For detailed patterns including server protection and package-internal panic/recover, see references/PANIC-RECOVER.md.


Quick Reference

Pattern Rule
Interface compliance var _ Interface = (*Type)(nil)
Receiving slices/maps Copy before storing
Returning slices/maps Return a copy
Resource cleanup Use defer
Defer argument timing Evaluated at defer, not call time
Enums Start at iota + 1
Time instants Use time.Time
Time durations Use time.Duration
Mutable globals Use dependency injection
Type embedding Use explicit delegation
Serialization Always use field tags
Key generation Use crypto/rand, never math/rand
Panic usage Only for truly unrecoverable situations
Recover pattern Use in defer; convert to error at API boundary

See Also

  • go-style-core – Core Go style principles
  • go-concurrency – Goroutine and channel patterns
  • go-error-handling – Error handling best practices