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:
@@ -110,6 +110,33 @@ build time.
|
||||
**Why:** Catches interface drift at compile time without creating an instance. The
|
||||
blank identifier discards the value — this is purely a static assertion.
|
||||
|
||||
**When to Use**
|
||||
|
||||
**Triggers:**
|
||||
- You've defined a type that MUST satisfy an interface (implements `io.Reader`, `http.Handler`, etc.)
|
||||
- You want a compile-time guarantee that catches drift when you add/remove methods
|
||||
- You're writing a package with multiple types implementing the same interface
|
||||
|
||||
**Example — before:**
|
||||
```go
|
||||
type MyWriter struct{ buf bytes.Buffer }
|
||||
|
||||
func (w *MyWriter) Write(p []byte) (int, error) { return w.buf.Write(p) }
|
||||
|
||||
// Months later, someone renames Write to WriteBuf...
|
||||
// No compile error — only discovered at runtime when passed as io.Writer
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```go
|
||||
// Compile-time check: if MyWriter stops implementing io.Writer, this fails to build
|
||||
var _ io.Writer = (*MyWriter)(nil)
|
||||
|
||||
type MyWriter struct{ buf bytes.Buffer }
|
||||
|
||||
func (w *MyWriter) Write(p []byte) (int, error) { return w.buf.Write(p) }
|
||||
```
|
||||
|
||||
**Anti-pattern:** Relying on tests to catch interface conformance; skipping the check
|
||||
and discovering the mismatch at runtime; using reflection.
|
||||
|
||||
@@ -312,6 +339,39 @@ are exhaustively listed together.
|
||||
**Why:** Type safety (can't accidentally pass an `os.Flag` where a `crypto.Hash` is
|
||||
expected). `iota` eliminates magic numbers. Grouping makes the full set visible.
|
||||
|
||||
**When to Use**
|
||||
|
||||
**Triggers:**
|
||||
- You have a set of related values that represent distinct states or options (status codes, modes, categories)
|
||||
- Raw integers would be meaningless to readers (`SetMode(3)` vs `SetMode(ModeAsync)`)
|
||||
- You want the type system to prevent passing a "color" where a "direction" is expected
|
||||
|
||||
**Example — before:**
|
||||
```go
|
||||
func SetLogLevel(level int) { ... }
|
||||
|
||||
// Caller:
|
||||
SetLogLevel(3) // what does 3 mean?!
|
||||
SetLogLevel(-1) // valid? who knows
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```go
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
LevelDebug LogLevel = iota
|
||||
LevelInfo
|
||||
LevelWarn
|
||||
LevelError
|
||||
)
|
||||
|
||||
func SetLogLevel(level LogLevel) { ... }
|
||||
|
||||
// Caller:
|
||||
SetLogLevel(LevelWarn) // self-documenting
|
||||
```
|
||||
|
||||
**Anti-pattern:** Untyped numeric constants; separate `const` declarations for related
|
||||
values; using raw integers in function signatures.
|
||||
|
||||
@@ -382,6 +442,34 @@ accidental mixing with raw int64 values.
|
||||
nanoseconds where seconds are expected. Methods provide conversion and formatting.
|
||||
Constants like `time.Second` make intent clear.
|
||||
|
||||
**When to Use**
|
||||
|
||||
**Triggers:**
|
||||
- A primitive type (`int`, `string`, `float64`) has a specific **semantic meaning** in your domain
|
||||
- You want to attach methods (formatting, validation, arithmetic) to the value
|
||||
- You've seen bugs from accidentally mixing units (`int` could be seconds, milliseconds, or nanoseconds)
|
||||
|
||||
**Example — before:**
|
||||
```go
|
||||
func SetTimeout(ms int) { ... } // is this milliseconds? seconds?
|
||||
func SetRetries(n int) { ... } // can't accidentally swap these... or CAN you?
|
||||
|
||||
SetTimeout(5) // 5 what?
|
||||
SetRetries(500) // oops, swapped arguments — compiles fine!
|
||||
```
|
||||
|
||||
**Example — after:**
|
||||
```go
|
||||
type Timeout time.Duration
|
||||
type RetryCount int
|
||||
|
||||
func SetTimeout(t Timeout) { ... }
|
||||
func SetRetries(n RetryCount) { ... }
|
||||
|
||||
SetTimeout(Timeout(5 * time.Second)) // explicit units
|
||||
SetRetries(RetryCount(3)) // can't swap — different types
|
||||
```
|
||||
|
||||
**Anti-pattern:** Using raw `int64` for durations; accepting `int` parameters for
|
||||
time intervals; mixing units (milliseconds in one place, seconds in another).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user