docs: add 'when to use' triggers + examples to all patterns

Added 'When to Use' subsections with concrete decision triggers and
before/after Go code examples to patterns across all directories:

- patterns/error-handling.md (3 patterns: sentinels, wrapping, Join)
- patterns/concurrency.md (4 patterns: Mutex, Once, done channels, pipelines)
- patterns/interfaces.md (4 patterns: small interfaces, accept/return, adapter, optional)
- patterns/structs.md (3 patterns: zero-value, constructors, config structs)
- patterns/package-design.md (3 patterns: internal/, init(), context keys)
- patterns/style.md (3 patterns: interface checks, iota constants, named types)
- patterns/testing-advanced.md (3 patterns: table tests, golden files, httptest)
- patterns/api-conventions.md (3 patterns: Must, layered API, graceful shutdown)
- patterns/documentation.md (2 patterns: examples, deprecated)
- kubernetes/patterns.md (3 patterns: controller, workqueue, leader election)
- kubernetes/production-go.md (2 patterns: codegen, HandleCrash)
- smells/anti-patterns.md (2 anti-patterns: cache mutation, edge-triggered)
This commit is contained in:
2026-04-30 12:07:40 +00:00
parent 0e5974f39a
commit eb9171368b
12 changed files with 1163 additions and 0 deletions
+95
View File
@@ -13,6 +13,39 @@ explicit initialization. Nil fields fall back to sensible defaults at method cal
self-documenting about its defaults. Users can write `var c http.Client` and start
making requests.
**When to Use**
**Triggers:**
- You're designing a type where the "empty" or "default" state is meaningful and safe
- Users should be able to write `var x MyType` and immediately call methods
- Your struct's nil/zero fields can fall back to sensible defaults at call time
**Example — before:**
```go
type Cache struct {
store map[string][]byte
ttl time.Duration
}
// Panics on zero value — store is nil!
func (c *Cache) Set(k string, v []byte) { c.store[k] = v }
```
**Example — after:**
```go
type Cache struct {
store map[string][]byte
ttl time.Duration // zero means no expiry
}
func (c *Cache) Set(k string, v []byte) {
if c.store == nil {
c.store = make(map[string][]byte) // lazy init on first use
}
c.store[k] = v
}
```
**Anti-pattern:** Requiring a constructor for basic use; panicking on zero-value use;
requiring all fields be set before the type is functional.
@@ -116,6 +149,39 @@ value alone.
clearly communicates what's required. The constructor can set internal invariants
(buffer sizes, split functions) that users shouldn't need to know about.
**When to Use**
**Triggers:**
- Your type has mandatory dependencies that can't be expressed as zero values (an `io.Reader`, a DB connection)
- Internal invariants must be set up (buffer allocation, goroutine start)
- The type isn't useful without initialization (unlike `sync.Mutex` or `bytes.Buffer`)
**Example — before:**
```go
type Parser struct {
lexer *Lexer
buf []Token
maxDepth int
}
// User must know about all internal state:
p := &Parser{lexer: NewLexer(input), buf: make([]Token, 0, 64), maxDepth: 100}
```
**Example — after:**
```go
func NewParser(input io.Reader) *Parser {
return &Parser{
lexer: NewLexer(input),
buf: make([]Token, 0, 64),
maxDepth: 100,
}
}
// User writes:
p := NewParser(file)
```
**Anti-pattern:** Forcing users to manually set unexported fields; having a constructor
that takes 10 optional parameters (use config struct instead); requiring New when
zero value would suffice.
@@ -205,6 +271,35 @@ configuration knobs. Nil/zero values always mean "use the default".
construct partially; serializable; the zero value works. This is Go's primary
configuration pattern (preferred over functional options in the stdlib).
**When to Use**
**Triggers:**
- Your constructor has 4+ optional parameters that would make a function signature unwieldy
- You want users to see all options in one place with godoc documentation
- Zero/nil values should mean "use the default" — no required fields beyond what the constructor demands
**Example — before:**
```go
// 7 parameters — impossible to remember the order
func NewServer(addr string, handler http.Handler, readTimeout, writeTimeout time.Duration,
maxConns int, logger *log.Logger, tlsConfig *tls.Config) *Server { ... }
```
**Example — after:**
```go
type ServerConfig struct {
Addr string // ":8080" if empty
Handler http.Handler // http.DefaultServeMux if nil
ReadTimeout time.Duration // zero means no timeout
WriteTimeout time.Duration // zero means no timeout
MaxConns int // 1000 if zero
Logger *log.Logger // log.Default() if nil
TLSConfig *tls.Config // plain HTTP if nil
}
func NewServer(cfg ServerConfig) *Server { ... }
```
**Anti-pattern:** Undocumented fields; requiring all fields set; using sentinel values
other than zero/nil for defaults; providing setters when direct assignment works.