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
+72
View File
@@ -52,6 +52,37 @@ Generated informers (note the header comment):
// Code generated by informer-gen. DO NOT EDIT.
```
### When to Use
**Triggers:**
- You have 10+ types that need identical boilerplate methods (DeepCopy, Validate, Marshal)
- Hand-writing the code is error-prone (forgetting to copy a new field causes silent bugs)
- The generated output is mechanical and reviewable, not creative
**Example — before:**
```go
// Hand-written deep copy for every type — 50 types × 30 lines each = 1500 lines of bugs
func (in *Deployment) DeepCopy() *Deployment {
out := new(Deployment)
out.Name = in.Name
out.Labels = make(map[string]string)
for k, v := range in.Labels { out.Labels[k] = v }
// Did you remember Annotations? Finalizers? Every nested struct?
}
```
**Example — after:**
```go
// +k8s:deepcopy-gen=true
type Deployment struct {
Name string
Labels map[string]string
Annotations map[string]string
}
// Generated: zz_generated.deepcopy.go handles ALL fields correctly, always.
// Adding a new field? Re-run generator. Zero chance of forgetting.
```
### Key Insight
**Stdlib has no code generation culture.** stdlib keeps things small enough that hand-writing works. Kubernetes proves that once you cross ~20 types with shared behavior, code gen is the only sane path.
@@ -329,6 +360,47 @@ In a production system with hundreds of goroutines, an unrecovered panic in one
- Allows cleanup handlers (shutdown gracefully)
- In tests, can be configured to not actually crash
### When to Use
**Triggers:**
- You're running multiple independent subsystems in one process (multiple controllers, background workers)
- A panic in one subsystem shouldn't kill the entire process
- You need structured logging of panic stack traces before potential recovery
**Example — before:**
```go
// One bad nil pointer in workerB kills workerA, workerC, and the whole server
func main() {
go workerA(ctx)
go workerB(ctx) // panics → entire process dies
go workerC(ctx)
select {}
}
```
**Example — after:**
```go
func safeGo(ctx context.Context, name string, f func(ctx context.Context)) {
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in %s: %v
%s", name, r, debug.Stack())
// Log, alert, increment metric — but don't kill siblings
}
}()
f(ctx)
}()
}
func main() {
safeGo(ctx, "worker-a", workerA)
safeGo(ctx, "worker-b", workerB) // panics → logged, other workers continue
safeGo(ctx, "worker-c", workerC)
select {}
}
```
### Key Insight
Stdlib's approach is "let it crash." Kubernetes' approach is "catch it, log it, let the controller retry on the next sync." This is only safe because the controller pattern is idempotent.