fix: add 'When to use' to every pattern (was missing)

This commit is contained in:
Rodin
2026-04-30 13:29:27 -07:00
parent e6fbfced96
commit 9ff22d2eed
+32
View File
@@ -42,6 +42,9 @@ defmodule Code.Formatter do
**Why:** Hides from docs. Signals "this is implementation, not API."
The Elixir team uses this for 30+ internal modules.
**When to use:** Modules that are implementation details — adapters,
internal state machines, compiler passes.
**When NOT to use:** If ANYONE outside your team might call it. Public
API must have docs.
@@ -69,6 +72,9 @@ end
extension point for "I made a new data structure and want it to work
with Enum."
**When to use:** When third-party code needs to extend your library
for new data types.
**When NOT to use:** When you control all implementations. Use
behaviours instead. Protocols are for open extension; behaviours are
for closed contracts.
@@ -88,6 +94,9 @@ only 6 in 15 years.
**Why:** Each protocol is a permanent API commitment. Once defined,
every type in the ecosystem may implement it.
**When to use:** When you need a small set of polymorphic operations
that ANY type in the ecosystem should be able to implement.
**When NOT to use:** Don't define a protocol for something only your
app needs. A behaviour or function argument is cheaper.
@@ -114,6 +123,9 @@ end
**Why:** Pattern matching makes handling explicit. The caller MUST
decide what to do with errors — they can't accidentally ignore them.
**When to use:** Any operation that interacts with the outside world
(files, network, databases) where failure is a normal outcome.
**When NOT to use:** For programmer errors (bugs). Those should raise.
`File.read!` raises; `File.read` returns tuples.
@@ -133,6 +145,9 @@ File.read!("path") # => "..." | raises File.Error
it doesn't, crash." This is intentional — crashing is the correct
response to unexpected errors in OTP.
**When to use:** Pipeline code where you want to crash on unexpected
failure rather than propagate error tuples.
**When NOT to use:** When failure is expected and the caller should
handle it (use tagged tuples).
@@ -163,6 +178,9 @@ end
**Why:** Inheritance via `use` is how Phoenix's ConnCase/DataCase work.
This pattern comes directly from ExUnit itself.
**When to use:** When 3+ test modules share the same setup callbacks,
imports, or lifecycle management.
**When NOT to use:** For helpers that don't need lifecycle callbacks.
Simple `import` is cleaner for utility functions.
@@ -174,6 +192,9 @@ Simple `import` is cleaner for utility functions.
most tests CAN be async — only database/file/process tests need
serialization.
**When to use:** Pure unit tests, tests with isolated process state,
tests that only read shared resources.
**When NOT to use:** Tests that modify global state, shared files, or
named processes.
@@ -197,6 +218,9 @@ def all?(enumerable) when is_list(enumerable) do
**Why:** Specs enable Dialyzer checking. Docs generate ExDoc pages. The
Elixir stdlib has zero undocumented public functions.
**When to use:** Every public function in every public module.
No exceptions.
**When NOT to use:** `@moduledoc false` modules skip docs (they're
internal).
@@ -228,6 +252,8 @@ Without `@type t`, struct fields are effectively untyped.
**Why:** A module IS a thing. Functions are what you DO with that thing.
`String.split/2` reads as "take a String, split it."
**When to use:** Every module you define. Period.
**When NOT to use:** Mix tasks (they're commands: `Mix.Tasks.Deps.Get`).
### Functions Are Verbs
@@ -269,6 +295,9 @@ end
**Why:** GenServer gives you: message serialization, supervision tree
integration, hot code upgrade, `:sys` debugging.
**When to use:** When you need mutable state that survives across
multiple requests or events.
**When NOT to use:** Stateless transformations (just use functions).
One-off concurrent work (use Task). Accumulating state within a request
(use recursion or reduce).
@@ -288,6 +317,9 @@ end
**Why:** Agent is GenServer with the common case optimized. Less
boilerplate for "I just need a mutable box."
**When to use:** When your process only needs get/update operations
on a single piece of state — no complex message handling.
**When NOT to use:** When you need `handle_info`, timeouts, or multiple
operations that must be atomic.