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
+47
View File
@@ -18,6 +18,30 @@ deploymentCopy := deployment.DeepCopy()
deploymentCopy.Spec.Replicas = ptr.To[int32](3)
```
### When to Apply This Rule
**Triggers:**
- You're reading from any shared data structure (cache, registry, concurrent map)
- Your function modifies a struct that was returned by a Lister, cache, or lookup
- You see code like `obj.Field = newValue` where `obj` came from a shared source
**Example — detecting the smell:**
```go
// This line is the red flag: modifying something obtained from a shared cache
deployment, _ := deploymentLister.Get(name)
deployment.Spec.Replicas = ptr.To[int32](5) // SMELL: mutating cached object
client.Update(ctx, deployment)
```
**Example — fixed:**
```go
deployment, _ := deploymentLister.Get(name)
copy := deployment.DeepCopy() // isolate your mutation
copy.Spec.Replicas = ptr.To[int32](5)
client.Update(ctx, copy)
```
**Evidence:** The `runtime.Object` interface *mandates* `DeepCopyObject()`. Every API type has generated deep copy methods. The entire architecture assumes immutable reads.
---
@@ -50,6 +74,29 @@ func (q *Typed[T]) Get() (item T, shutdown bool) {
**The pattern K8s enforces:** Level-triggered reconciliation. The `syncHandler` reads *current state from the cache*, computes *desired state from the spec*, and makes the world match:
### When to Apply This Rule
**Triggers:**
- Your handler says "X happened, so do Y" instead of "the world should look like Z, make it so"
- You're incrementing/decrementing counters based on events instead of counting actual state
- A missed event (network blip, restart) would cause permanent drift
**Example — detecting the smell:**
```go
func onPodDeleted(pod Pod) {
deployment.Status.Replicas-- // edge-triggered: if we miss a delete, count is wrong forever
}
```
**Example — fixed:**
```go
func reconcile(deployment Deployment) {
actualPods := listPodsForDeployment(deployment)
deployment.Status.Replicas = int32(len(actualPods)) // level-triggered: always correct
}
```
```go
// The sync function always reads current state, never relies on "what happened"
func (dc *DeploymentController) syncDeployment(ctx context.Context, key string) error {