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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user