update links under the root README file

This commit is contained in:
medunes
2026-01-04 20:53:07 +01:00
parent 60a6e52449
commit 5b81ba8200
23 changed files with 53 additions and 10 deletions

View File

@@ -0,0 +1,47 @@
# Kata 08: The Retry Policy That Respects Context
**Target Idioms:** Retry Classification, Error Wrapping (`%w`), Timer Reuse, Context Deadlines
**Difficulty:** 🟡 Intermediate
## 🧠 The "Why"
In other languages, retries are often hidden in SDKs. In Go, its easy to write:
- infinite retry loops,
- retry-on-any-error (bad),
- retry that ignores context cancellation (worse),
- retry implemented with repeated `time.Sleep` (hard to test, wasteful).
This kata makes you implement a **testable**, **context-aware** retry loop.
## 🎯 The Scenario
You call a flaky downstream service. You should retry only on **transient** failures:
- `net.Error` with `Timeout() == true`
- HTTP 429 / 503 (if you model HTTP)
- sentinel `ErrTransient`
Everything else must fail immediately.
## 🛠 The Challenge
Implement:
- `type Retryer struct { ... }`
- `func (r *Retryer) Do(ctx context.Context, fn func(context.Context) error) error`
### 1. Functional Requirements
- [ ] Retries up to `MaxAttempts`.
- [ ] Uses exponential backoff: `base * 2^attempt` with a max cap.
- [ ] Optional jitter (deterministic in tests).
- [ ] Stops immediately on `ctx.Done()`.
### 2. The "Idiomatic" Constraints (Pass/Fail Criteria)
- [ ] **Must NOT** call `time.Sleep` inside the retry loop.
- [ ] **Must** use a `time.Timer` and `Reset` it (timer reuse).
- [ ] **Must** wrap the final error with context (attempt count) using `%w`.
- [ ] **Must** classify errors using `errors.Is` / `errors.As`.
## 🧪 Self-Correction (Test Yourself)
- **If context cancellation only stops after the sleep:** you failed.
- **If you retry non-transient errors:** you failed classification.
- **If you cant test it without real time:** inject time/jitter sources.
## 📚 Resources
- https://go.dev/blog/go1.13-errors
- https://pkg.go.dev/errors
- https://pkg.go.dev/time

View File

@@ -0,0 +1,59 @@
# Kata 19: The Cleanup Chain (defer + LIFO + Error Preservation)
**Target Idioms:** `defer` Discipline, Named Returns, Error Composition (`errors.Join`), Close/Rollback Ordering
**Difficulty:** 🟡 Intermediate
## 🧠 The "Why"
`defer` is easy to misuse:
- deferring in loops (resource spikes),
- ignoring `Close()` / `Rollback()` errors,
- losing the original failure when cleanup also fails,
- wrong cleanup ordering (commit then rollback nonsense).
Idiomatic Go keeps cleanup local, ordered, and preserves important errors.
## 🎯 The Scenario
You implement `BackupDatabase`:
- open output file
- connect DB
- begin transaction
- stream rows to file
- commit
If anything fails, you must close/rollback what was already acquired.
## 🛠 The Challenge
Implement:
- `func BackupDatabase(ctx context.Context, dbURL, filename string) (err error)`
Use mock interfaces for DB + Tx + Rows if you want (recommended).
### 1. Functional Requirements
- [ ] Open file for writing.
- [ ] Connect to DB.
- [ ] Begin Tx.
- [ ] Write data (simulate streaming).
- [ ] Commit on success.
- [ ] On failure: rollback + close resources in correct order.
### 2. The "Idiomatic" Constraints (Pass/Fail Criteria)
- [ ] **Defer cleanup immediately after acquisition.**
- [ ] **No manual cleanup paths** except by controlling flags (e.g., `committed bool`) used by deferred funcs.
- [ ] **Preserve both errors:** if main operation fails and cleanup fails too, return a combined error (`errors.Join`).
- [ ] **Named return `err`** so defers can amend it safely.
- [ ] **No defer-in-loop for per-row resources:** if your mock has per-row closers, show the correct pattern.
## 🧪 Self-Correction (Test Yourself)
1. **Tx Begin Fails**
- Make `Begin()` error.
- **Pass:** file + db connection still close.
2. **Commit Fails + Close Fails**
- Make `Commit()` return error and also make `file.Close()` return error.
- **Pass:** returned error clearly contains both (use `errors.Join`).
3. **No FD Leak**
- Run 1000 times.
- **Pass:** file descriptors dont grow.
## 📚 Resources
- https://go.dev/blog/defer-panic-and-recover
- https://go.dev/doc/go1.20 (errors.Join)

View File

@@ -0,0 +1,54 @@
# Kata 20: The “nil != nil” Interface Trap (Typed nil Errors)
**Target Idioms:** Interface Semantics, Typed nil Pitfall, Safe Error Returns, `errors.As`
**Difficulty:** 🔴 Advanced
## 🧠 The "Why"
In Go, an interface value is only nil when **both** its dynamic type and value are nil.
If you return a **typed nil pointer** (e.g., `(*MyError)(nil)`) as an `error`, the interface has a non-nil type, so `err != nil` becomes true even though the pointer inside is nil.
This bites real code in production (especially custom error types and factories).
## 🎯 The Scenario
A function returns `error`. Sometimes it returns a typed nil pointer.
Your caller checks `if err != nil` and takes an error path, logs misleading failures, or even panics when accessing fields/methods.
## 🛠 The Challenge
Write a minimal package that:
1) demonstrates the bug, and
2) fixes it with an idiomatic pattern.
### 1. Functional Requirements
- [ ] Implement `type MyError struct { Op string }` (or similar).
- [ ] Implement a function `DoThing(...) error` that **sometimes returns** `(*MyError)(nil)` as `error`.
- [ ] Demonstrate:
- `err != nil` is true
- `fmt.Printf("%T %#v\n", err, err)` shows the typed nil behavior
- [ ] Provide a corrected version that returns a true nil interface when there is no error.
### 2. The "Idiomatic" Constraints (Pass/Fail Criteria)
- [ ] **Must show the failing behavior** in a test (`go test`).
- [ ] **Must show the fix** in a test.
- [ ] **Must not “fix” by panicking or by sentinel errors.**
- [ ] Use one of these idiomatic fixes:
- return `nil` explicitly when the pointer is nil
- or return `error(nil)` in the relevant branch
- [ ] Demonstrate safe extraction using:
- `var me *MyError; errors.As(err, &me)` and check `me != nil`
## 🧪 Self-Correction (Test Yourself)
1. **The Trap Repro**
- Make `DoThing()` return `var e *MyError = nil; return e`
- **Pass:** your test proves `err != nil` is true.
2. **The Fix**
- If internal pointer is nil, return literal `nil`.
- **Pass:** `err == nil` works, callers behave correctly.
3. **Extraction Safety**
- Wrap the error and still extract with `errors.As`.
- **Pass:** extraction works through wrapping layers.
## 📚 Resources
- https://go.dev/blog/laws-of-reflection (interface basics)
- https://go.dev/blog/go1.13-errors (errors.As)
- https://forum.golangbridge.org/t/logic-behind-failing-nil-check/16331