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