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
+82
View File
@@ -117,6 +117,32 @@ Packages under `internal/` can only be imported by code rooted at the parent of
- `net/http/internal/ascii` → importable by `net/http` and children
- NOT importable by `net/url` or any other package
### When to Use
**Triggers:**
- You have helper code shared between sub-packages but NOT part of your public API
- You're tempted to export a function "just for testing" — put it in `internal/` instead
- Your package has grown and you want to split it without committing to new public APIs
**Example — before:**
```go
// pkg/mylib/helpers.go — exported just so pkg/mylib/sub can use it
package mylib
func ParseInternalFormat(s string) Thing { ... } // now anyone can depend on this!
```
**Example — after:**
```go
// pkg/mylib/internal/parse/parse.go
package parse
func InternalFormat(s string) Thing { ... } // only importable by pkg/mylib and children
// pkg/mylib/sub/handler.go
import "pkg/mylib/internal/parse" // ✓ allowed
```
### Anti-pattern
```go
@@ -192,6 +218,34 @@ The stdlib uses `init()` for:
3. Keep them short
4. Prefer explicit initialization in `main()` when possible
### When to Use
**Triggers:**
- You're writing a driver or plugin that needs to register itself with a central registry on import
- The registration is side-effect-only (no return value, can't fail)
- You want `import _ "mydb/driver"` to make the driver available without explicit setup
**Example — before:**
```go
// main.go — user must manually register every driver
func main() {
postgres.Register() // easy to forget
mysql.Register() // order matters?
sqlite.Register()
}
```
**Example — after:**
```go
// postgres/driver.go
func init() {
sql.Register("postgres", &Driver{}) // auto-registers on import
}
// main.go — import for side-effect
import _ "github.com/lib/pq" // driver registers itself
```
### Anti-pattern
```go
@@ -404,6 +458,34 @@ type contextKey struct {
- **Type-safe accessors** avoid repeated type assertions
- **Pointer-based keys** guarantee uniqueness
### When to Use
**Triggers:**
- You need to pass request-scoped metadata through a call chain (user ID, trace ID, auth token)
- The data crosses package boundaries and isn't appropriate as a function parameter
- You want type safety — only your package should read/write its context values
**Example — before:**
```go
// String keys — any package can collide or access your values
ctx = context.WithValue(ctx, "userID", 42)
uid := ctx.Value("userID").(int) // panics if wrong type or missing
```
**Example — after:**
```go
type ctxKey struct{}
func WithUserID(ctx context.Context, id int) context.Context {
return context.WithValue(ctx, ctxKey{}, id)
}
func UserID(ctx context.Context) (int, bool) {
id, ok := ctx.Value(ctxKey{}).(int)
return id, ok
}
```
### Anti-pattern
```go