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