diff --git a/patterns/from-source.md b/patterns/from-source.md index 793a0e2..1100884 100644 --- a/patterns/from-source.md +++ b/patterns/from-source.md @@ -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.