# Documentation Patterns Patterns extracted from the Elixir standard library source code. ## Contents 1. [@moduledoc with Structured Sections](#1-moduledoc-with-structured-sections) 2. [@doc with Sections and Examples](#2-doc-with-sections-and-examples) 3. [@doc since: Version Annotation](#3-doc-since-version-annotation) 4. [@doc guard: true Metadata](#4-doc-guard-true-metadata) 5. [@doc false — Hiding from Documentation](#5-doc-false--hiding-from-documentation) 6. [@moduledoc false — Hiding Modules](#6-moduledoc-false--hiding-modules) 7. [Mermaid Diagrams in Documentation](#7-mermaid-diagrams-in-documentation) 8. [Admonition Blocks in Documentation](#8-admonition-blocks-in-documentation) 9. [@doc deprecated: Soft Deprecation](#9-doc-deprecated-soft-deprecation) 10. [Callback Documentation Convention](#10-callback-documentation-convention) 11. [Documentation with Link References (c: and t: prefixes)](#11-documentation-with-link-references-c-and-t-prefixes) --- ## 1. @moduledoc with Structured Sections **Source:** [lib/elixir/lib/gen_server.ex#L6](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L6)+, [lib/logger/lib/logger.ex#L6](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/logger/lib/logger.ex#L6)+ **What it does:** `@moduledoc` uses a clear hierarchical structure with `##` sections covering: overview, examples, configuration, and detailed behavioral explanations. GenServer covers "Example", "Client / Server APIs", "How to supervise", "Name registration", "Timeouts", "Debugging". Logger covers "Levels", "Message", "Metadata", "Configuration" (with subsections for Boot/Compile/Runtime). **Why:** Long moduledocs are effectively reference manuals. Structured sections let users jump to what they need. The pattern establishes a convention: start with "what is this" → "quick example" → "detailed topics." **Anti-pattern:** Writing a single unstructured wall of text, or documenting only the happy path without covering error handling, configuration, and advanced usage. **Code example:** ```elixir defmodule GenServer do @moduledoc """ A behaviour module for implementing the server of a client-server relation. A GenServer is a process like any other Elixir process... ## Example The GenServer behaviour abstracts the common client-server interaction... defmodule Stack do use GenServer # ... end ## How to supervise A GenServer is most commonly started under a supervision tree... ## Name registration ... ## Timeouts ... ## Debugging with the :sys module ... """ end ``` ### When to Use **Triggers:** - Your module is the primary entry point for a concept (GenServer, Logger, Supervisor) - The module has more than 3-4 public functions with non-obvious relationships - Users need to understand configuration, lifecycle, or architecture before calling individual functions **Example — before:** ```elixir defmodule MyApp.Cache do @moduledoc "A cache module." def get(key), do: ... def put(key, value, opts), do: ... def invalidate(key), do: ... end ``` **Example — after:** ```elixir defmodule MyApp.Cache do @moduledoc """ A write-through cache backed by ETS with configurable TTL. ## Usage MyApp.Cache.put("user:1", user, ttl: :timer.minutes(5)) MyApp.Cache.get("user:1") #=> {:ok, %User{}} ## Configuration Configure in your application config: config :my_app, MyApp.Cache, max_size: 10_000, default_ttl: :timer.hours(1) ## Eviction When `max_size` is reached, entries are evicted LRU-first... """ end ``` ### When NOT to Use **Don't use this when:** - The module has 1-2 functions and the purpose is obvious from the module name - It's an internal/private module (`@moduledoc false` is better) - The module is a thin wrapper where function-level docs suffice **Over-application example:** ```elixir defmodule MyApp.StringUtils do @moduledoc """ # String Utilities ## Overview This module provides string utility functions for the application. ## Architecture Functions in this module operate on binaries and return binaries... ## Configuration No configuration required. ## Examples See individual function documentation. """ def capitalize_words(str), do: ... end ``` **Better alternative:** ```elixir defmodule MyApp.StringUtils do @moduledoc "String transformation helpers for display formatting." @doc "Capitalizes the first letter of each word." def capitalize_words(str), do: ... end ``` **Why:** A utility module with a few pure functions doesn't need architecture docs, configuration sections, or a table of contents. Match the documentation depth to the module's complexity. --- ## 2. @doc with Sections and Examples **Source:** [lib/elixir/lib/kernel.ex#L315](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L315) (abs/1), [lib/logger/lib/logger.ex#L536](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/logger/lib/logger.ex#L536) **What it does:** `@doc` for individual functions includes: a brief one-liner, allowed-in-guards note (via `@doc guard: true`), explanation of edge cases, and `## Examples` with doctests. **Why:** Each function doc is self-contained. A developer reading docs shouldn't need to look elsewhere for basic usage. Guard-eligible functions are annotated structurally so tools can surface this. **Anti-pattern:** Docs that say "see module documentation" without providing any local context. Also: examples without `iex>` that can't be tested automatically. **Code example:** ```elixir @doc """ Returns all the available levels. """ @doc since: "1.16.0" @spec levels() :: [level(), ...] def levels(), do: @levels ``` ### When to Use **Triggers:** - The function has non-obvious behavior, edge cases, or preconditions - Users need to understand what happens with boundary inputs (nil, empty list, negative numbers) - The function is part of a public API that others will call without reading the source **Example — before:** ```elixir def chunk(list, size), do: ... ``` **Example — after:** ```elixir @doc """ Splits `list` into chunks of the given `size`. The last chunk may have fewer than `size` elements if the list length is not evenly divisible. ## Examples iex> chunk([1, 2, 3, 4, 5], 2) [[1, 2], [3, 4], [5]] iex> chunk([], 3) [] """ def chunk(list, size), do: ... ``` ### When NOT to Use **Don't use this when:** - The function is private (use `# comments` for private function notes) - The function name + typespec are completely self-explanatory (e.g., `@spec pid() :: pid()`) - You're implementing a behaviour callback — `@impl true` sets `@doc false` automatically. Override only when the implementation has semantics the behaviour can't speak to (see [Pattern 10 — implementation-side docs](#when-to-override-doc-false-on-impl-functions)) **Over-application example:** ```elixir @doc """ Returns the name. ## Examples iex> get_name(%User{name: "Alice"}) "Alice" """ @spec get_name(User.t()) :: String.t() def get_name(%User{name: name}), do: name ``` **Better alternative:** ```elixir @spec get_name(User.t()) :: String.t() def get_name(%User{name: name}), do: name ``` **Why:** When the function name, spec, and implementation are all trivially obvious, a `@doc` that restates them adds maintenance burden without helping anyone. Save detailed docs for functions where the behavior isn't immediately obvious. --- ## 3. @doc since: Version Annotation **Source:** [lib/logger/lib/logger.ex#L539](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/logger/lib/logger.ex#L539), 576, 813, 824, 831, [lib/elixir/lib/kernel.ex#L5163](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L5163)+ **What it does:** Attaches `since: "X.Y.Z"` metadata to `@doc` indicating the Elixir version that introduced the function. **Why:** Users working with multiple Elixir versions need to know API availability. ExDoc renders this prominently. The pattern is universal in the standard library for any function added after 1.0. **Anti-pattern:** Adding new public functions without `@since` annotation, leaving version compatibility ambiguous. **Code example:** ```elixir @doc since: "1.15.0" @spec default_formatter(formatter_opts) :: {module, :logger.formatter_config()} def default_formatter(overrides \\ []) when is_list(overrides) do # ... end @doc since: "1.14.0" def binary_slice(binary, start, size) when is_binary(binary) and is_integer(start) and is_integer(size) and size >= 0 do # ... end ``` ### When to Use **Triggers:** - You're adding a new public function to an existing library (anything post-1.0) - Your library publishes versioned documentation (via ExDoc/HexDocs) - Users may be on older versions and need to know when a function became available **Example — before:** ```elixir @doc "Compacts the list by removing nil values." def compact(list), do: Enum.reject(list, &is_nil/1) ``` **Example — after:** ```elixir @doc since: "1.4.0" @doc "Compacts the list by removing nil values." def compact(list), do: Enum.reject(list, &is_nil/1) ``` ### When NOT to Use **Don't use this when:** - You're writing application code (not a published library) - The function has existed since the library's first release - The library doesn't publish versioned docs or follow semver **Over-application example:** ```elixir # In a Phoenix controller — no one checks "which version of my app added this" defmodule MyAppWeb.UserController do @doc since: "0.1.0" def index(conn, _params), do: ... @doc since: "0.2.0" def show(conn, %{"id" => id}), do: ... end ``` **Better alternative:** ```elixir defmodule MyAppWeb.UserController do def index(conn, _params), do: ... def show(conn, %{"id" => id}), do: ... end ``` **Why:** `since:` annotations help library consumers check compatibility. Application code is deployed atomically — there's no concept of "which version of the app introduced this endpoint." Use git blame instead. --- ## 4. @doc guard: true Metadata **Source:** [lib/elixir/lib/kernel.ex#L329](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/kernel.ex#L329), 408, 428, 452, etc. **What it does:** Functions/macros usable in guard clauses are annotated with `@doc guard: true` metadata, separate from the doc string itself. **Why:** This is machine-readable metadata. ExDoc and IEx can filter/display guard-eligible functions differently. It's orthogonal to the prose documentation. **Anti-pattern:** Mentioning "allowed in guards" only in the doc string text, which tools can't parse programmatically. **Code example:** ```elixir @doc """ Returns the absolute value of `number`. ... ## Examples iex> abs(-1) 1 iex> abs(1) 1 """ @doc guard: true @spec abs(number) :: number def abs(number) when is_number(number), do: ... ``` ### When to Use **Triggers:** - The function/macro is valid in guard clauses - You want tools (ExDoc, IEx) to programmatically identify guard-eligible functions - The function is a Kernel macro that users might try to use in guards **Example — before:** ```elixir @doc """ Returns true if `term` is a non-empty binary. Allowed in guard tests. """ defmacro is_non_empty_binary(term) do ... end ``` **Example — after:** ```elixir @doc """ Returns true if `term` is a non-empty binary. """ @doc guard: true defmacro is_non_empty_binary(term) do ... end ``` ### When NOT to Use **Don't use this when:** - The function is NOT valid in guard clauses (adding it would be misleading) - You're writing application code where no one filters functions by guard eligibility - The function calls other functions or has side effects (it can't be a guard) **Over-application example:** ```elixir @doc guard: true def valid_email?(email) do String.contains?(email, "@") # NOT guard-safe — calls String.contains? end ``` **Better alternative:** ```elixir @doc "Checks if the string looks like an email address." def valid_email?(email) do String.contains?(email, "@") end ``` **Why:** `@doc guard: true` is a semantic contract that the function works in guard position. Adding it to non-guard functions breaks tooling expectations and confuses users who try to use it in `when` clauses. --- ## 5. @doc false — Hiding from Documentation **Source:** [lib/elixir/lib/inspect.ex#L410](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/inspect.ex#L410), 417; implicit via `@impl true` **What it does:** `@doc false` explicitly hides a function from documentation generators. Also, using `@impl true` automatically sets `@doc false` (unless `@doc` is explicitly provided). **Why:** Not all public functions are part of the user-facing API. Protocol implementations, internal helpers that must be public for technical reasons, and overridable defaults should be hidden from docs. **Anti-pattern:** Leaving documentation on functions that are public only for technical reasons (e.g., protocol dispatch functions), confusing users about what to call. **Code example:** ```elixir # Explicit hiding: @doc false def __protocol__(:module), do: __MODULE__ # Implicit via @impl: @impl true # automatically sets @doc false def init(counter) do {:ok, counter} end ``` ### When to Use **Triggers:** - A function must be public for technical reasons (protocol dispatch, macro expansion) but isn't part of the user API - You're implementing callbacks with `@impl true` and don't want generated docs - The function is an internal hook that users should never call directly **Example — before:** ```elixir # Users see this in docs and try to call it def __struct__(fields), do: ... ``` **Example — after:** ```elixir @doc false def __struct__(fields), do: ... ``` ### When NOT to Use **Don't use this when:** - The function IS part of the public API (even if you think it's "obvious") - You want to discourage use but still document it (use `@doc deprecated:` instead) - You're hiding functions because you're too lazy to document them - The `@impl` function has implementation-specific semantics that callers need to know (see [Pattern 10 — implementation-side docs](#when-to-override-doc-false-on-impl-functions)) **Over-application example:** ```elixir defmodule MyApp.Repo do @doc false def get(schema, id), do: ... @doc false def all(query), do: ... end ``` **Better alternative:** ```elixir defmodule MyApp.Repo do @doc "Fetches a single record by primary key. Returns nil if not found." def get(schema, id), do: ... @doc "Fetches all records matching the query." def all(query), do: ... end ``` **Why:** `@doc false` means "this function is not part of the public API." If users are expected to call it, it needs documentation. Hiding public API behind `@doc false` is a maintenance hazard — users will call undocumented functions and break on upgrades. > **Note:** `@impl true` auto-sets `@doc false`, but some implementations earn their own `@doc`. See [Pattern 10 — implementation-side docs](#when-to-override-doc-false-on-impl-functions) for the rule. --- ## 6. @moduledoc false — Hiding Modules **Source:** Common pattern in internal modules (not shown in top-level files but documented in `Module`) **What it does:** `@moduledoc false` makes an entire module invisible to documentation tools. **Why:** Internal implementation modules (e.g., `MyApp.Repo.Migrations.Internal`) exist for code organization but shouldn't appear in user-facing docs. **Anti-pattern:** Leaving `@moduledoc` undeclared on internal modules — ExDoc will show them with an empty documentation page, confusing users. **Code example:** ```elixir defmodule MyApp.Internal.Helper do @moduledoc false # This module exists but won't appear in docs def helper_function(x), do: x * 2 end ``` ### When to Use **Triggers:** - The module exists purely for internal code organization - It's a protocol implementation module that users never reference directly - It's a migration, task, or generated module that shouldn't appear in docs **Example — before:** ```elixir defmodule MyApp.Repo.Supervisor do # Internal supervisor — users never interact with this directly use Supervisor def start_link(opts), do: ... def init(opts), do: ... end ``` **Example — after:** ```elixir defmodule MyApp.Repo.Supervisor do @moduledoc false use Supervisor def start_link(opts), do: ... def init(opts), do: ... end ``` ### When NOT to Use **Don't use this when:** - The module has any function that users are expected to call - You're hiding it because documentation feels like too much work - The module is a behaviour that others will implement **Over-application example:** ```elixir defmodule MyApp.Telemetry do @moduledoc false # "It's just telemetry setup, nobody cares" def setup do # Attaches telemetry handlers that affect app behavior :telemetry.attach_many(...) end end ``` **Better alternative:** ```elixir defmodule MyApp.Telemetry do @moduledoc """ Telemetry event handlers for request metrics and error tracking. Called from `Application.start/2`. Attaches handlers for: - `[:my_app, :request, :stop]` — request duration histograms - `[:my_app, :error]` — error counters """ def setup, do: ... end ``` **Why:** If a module affects application behavior and other developers need to understand it during debugging or extension, it needs documentation. `@moduledoc false` should be reserved for truly mechanical/generated code. --- ## 7. Mermaid Diagrams in Documentation **Source:** [lib/elixir/lib/gen_server.ex#L14](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L14) **What it does:** Embeds Mermaid diagram syntax directly in `@moduledoc` to illustrate architectural patterns (client-server message flow). **Why:** Visual diagrams communicate architecture faster than prose. ExDoc renders Mermaid natively, so documentation can include live-rendered flowcharts. **Anti-pattern:** Describing complex interaction patterns only in prose when a diagram would be clearer. **Code example:** ```elixir @moduledoc """ A behaviour module for implementing the server of a client-server relation. ```mermaid graph BT C(Client #3) ~~~ B(Client #2) ~~~ A(Client #1) A & B & C -->|request| GenServer GenServer -.->|reply| A & B & C ``` ## Example ... """ ``` ### When to Use **Triggers:** - You're documenting a multi-component architecture (client-server, pipeline, state machine) - The relationship between components is spatial or sequential and hard to express in prose - ExDoc is your documentation renderer (it supports Mermaid natively) **Example — before:** ```elixir @moduledoc """ The pipeline processes events through three stages: first the parser, then the transformer, then the loader. The parser sends to transformer, transformer sends to loader. Errors at any stage go to the error handler. """ ``` **Example — after:** ```elixir @moduledoc """ Event processing pipeline with three stages. ```mermaid graph LR Parser -->|events| Transformer Transformer -->|records| Loader Parser & Transformer & Loader -.->|errors| ErrorHandler ``` ## Stages ... """ ``` ### When NOT to Use **Don't use this when:** - The module's architecture is simple enough that a one-sentence description suffices - Your docs aren't rendered by a tool that supports Mermaid (raw markdown viewers won't render it) - The diagram would be trivial (one box, one arrow) — a sentence is clearer **Over-application example:** ```elixir @moduledoc """ Wraps an integer counter. ```mermaid graph LR User -->|increment| Counter Counter -->|value| User ``` """ ``` **Better alternative:** ```elixir @moduledoc """ A simple integer counter. Call `increment/1` to add, `value/1` to read. """ ``` **Why:** Diagrams add cognitive overhead. If the reader can understand the architecture faster from a sentence than from parsing a diagram, the diagram is noise. Reserve diagrams for genuinely complex interactions. --- ## 8. Admonition Blocks in Documentation **Source:** [lib/elixir/lib/gen_server.ex#L88](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L88), [lib/elixir/lib/supervisor.ex#L34](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/supervisor.ex#L34) **What it does:** Uses markdown admonition syntax (`> #### Title {: .info}`) to highlight important callouts — especially for `use ModuleName` behavior documentation. **Why:** Admonitions draw attention to critical information that might otherwise be buried in prose. The `.info`, `.warning`, `.neutral` classes provide visual differentiation. **Anti-pattern:** Burying critical behavioral information (like what `use` does) in regular paragraphs that users skim over. **Code example:** ```elixir @moduledoc """ ... > #### `use GenServer` {: .info} > > When you `use GenServer`, the `GenServer` module will > set `@behaviour GenServer` and define a `child_spec/1` > function, so your module can be used as a child > in a supervision tree. ... """ ``` ### When to Use **Triggers:** - There's critical information that users MUST notice (breaking changes, security implications, required setup) - You need to document what `use MyModule` injects (the OTP convention) - A common mistake or foot-gun deserves visual prominence **Example — before:** ```elixir @moduledoc """ ... Note: calling process/1 with untrusted input can execute arbitrary code. Make sure to validate inputs first. ... """ ``` **Example — after:** ```elixir @moduledoc """ ... > #### Security Warning {: .warning} > > `process/1` evaluates its input. Never pass untrusted user input > directly — always validate and sanitize first. ... """ ``` ### When NOT to Use **Don't use this when:** - The information isn't actually critical (don't cry wolf) - You're using admonitions for every paragraph (they lose impact) - Your rendering target doesn't support admonition syntax (it'll render as ugly blockquotes) **Over-application example:** ```elixir @moduledoc """ > #### Overview {: .info} > > This module provides helper functions. > #### Usage {: .info} > > Call the functions with the appropriate arguments. > #### Note {: .neutral} > > All functions return their results. """ ``` **Better alternative:** ```elixir @moduledoc """ Helper functions for string formatting. ## Usage MyHelpers.format_name("alice") #=> "Alice" """ ``` **Why:** Admonitions are visual interrupts — they break reading flow to demand attention. When everything is an admonition, nothing is. Reserve them for information where missing it would cause real problems. --- ## 9. @doc deprecated: Soft Deprecation **Source:** [lib/elixir/lib/module.ex#L163](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/module.ex#L163) **What it does:** Uses `@doc deprecated: "Use X instead"` for soft deprecation (documentation-only warning) vs. `@deprecated "reason"` for hard deprecation (compiler warning). **Why:** Two levels of deprecation serve different needs. Soft deprecation signals "we recommend the alternative" without breaking builds. Hard deprecation actively warns at compile time. **Anti-pattern:** Using `@deprecated` for things that aren't truly deprecated yet (just discouraged), creating noise in build outputs. **Code example:** ```elixir # Soft deprecation — docs only, no compiler warning: @doc deprecated: "Use Kernel.length/1 instead" def size(keyword) do length(keyword) end # Hard deprecation — compiler warning emitted: @deprecated "Use Kernel.length/1 instead" def size(keyword) do length(keyword) end ``` ### When to Use **Triggers:** - You want to signal "prefer the alternative" without emitting compiler warnings - The function still works fine but a better API exists - You're planning a future hard deprecation and want to give users a migration window **Example — before:** ```elixir @doc "Fetches a user by ID. Deprecated: use Accounts.get_user/1." def fetch_user(id), do: ... ``` **Example — after:** ```elixir @doc deprecated: "Use Accounts.get_user/1 instead" @doc "Fetches a user by ID." def fetch_user(id), do: ... ``` ### When NOT to Use **Don't use this when:** - You actually want compiler warnings (use `@deprecated "reason"` instead) - The function is being removed in the next release (hard deprecation is appropriate) - The function is still the recommended approach (don't pre-deprecate) **Over-application example:** ```elixir # Deprecating before the replacement exists @doc deprecated: "Will be replaced by new_process/2 in v3.0" def process(input) do # new_process/2 doesn't exist yet! ... end ``` **Better alternative:** ```elixir # Wait until the replacement is available, then deprecate def process(input), do: ... # In v3.0, after new_process/2 ships: @doc deprecated: "Use new_process/2 instead" def process(input), do: new_process(input, []) ``` **Why:** Never deprecate a function before the recommended alternative exists and is documented. Users seeing "use X instead" need X to actually exist — otherwise you're creating confusion without offering a path forward. --- ## 10. Callback Documentation Convention **Source:** [lib/elixir/lib/gen_server.ex#L584](https://github.com/elixir-lang/elixir/blob/f4e1b34617ef92052b65781f18eae5b88a490098/lib/elixir/lib/gen_server.ex#L584) (handle_call docs) **What it does:** Each `@callback` is preceded by a comprehensive `@doc` that explains: what triggers the callback, what the parameters mean, every possible return value and its effect, when the callback is optional, and cross-references to related callbacks. **Why:** Callback documentation is the primary teaching tool for behaviour implementers. Since users must write these functions, the docs must be complete enough to implement from. **Anti-pattern:** Documenting callbacks with only a one-liner, forcing users to read source code or external guides. **Code example:** ```elixir @doc """ Invoked to handle synchronous `call/3` messages. `call/3` will block until a reply is received (unless the call times out or nodes are disconnected). `request` is the request message sent by a `call/3`, `from` is a 2-tuple containing the caller's PID and a term that uniquely identifies the call, and `state` is the current state of the `GenServer`. Returning `{:reply, reply, new_state}` sends the response `reply` to the caller and continues the loop with new state `new_state`. Returning `{:noreply, new_state}` does not send a response to the caller and continues the loop with new state `new_state`. The response must be sent with `reply/2`. ... This callback is optional. If one is not implemented, the server will fail if a call is performed against it. """ @callback handle_call(request :: term, from, state :: term) :: {:reply, reply, new_state} | ... when reply: term, new_state: term, reason: term ``` ### When to Use **Triggers:** - You're defining a behaviour that other modules will implement - Implementers need to know: when the callback fires, what all return values mean, and which callbacks are optional - The callback has multiple valid return shapes with different effects **Example — before:** ```elixir @callback handle_event(event :: term(), state :: term()) :: {:ok, term()} | {:error, term()} ``` **Example — after:** ```elixir @doc """ Invoked when an event is received from the event bus. `event` is the decoded event payload. `state` is the handler's current state, initialized by `c:init/1`. Returning `{:ok, new_state}` acknowledges the event and continues. Returning `{:skip, new_state}` skips without acknowledgment (redelivery possible). Returning `{:error, reason}` triggers the error handler configured in `start_link/1`. This callback is required. """ @callback handle_event(event :: term(), state :: term()) :: {:ok, new_state :: term()} | {:skip, new_state :: term()} | {:error, reason :: term()} ``` ### When NOT to Use **Don't use this when:** - The callback has a single obvious return type and the name says it all - You're documenting internal callbacks that aren't part of a public behaviour - The behaviour is private to your application and only you implement it **Over-application example:** ```elixir @doc """ Called to format the value. ## Parameters - `value` - The value to format (term) ## Returns - `String.t()` - The formatted string ## Examples iex> format(:hello) "hello" """ @callback format(value :: term()) :: String.t() ``` **Better alternative:** ```elixir @doc "Formats `value` into a human-readable string for display." @callback format(value :: term()) :: String.t() ``` **Why:** A callback with one parameter and one return type doesn't need a full reference manual. Match documentation depth to complexity — a one-liner with good naming is better than padded sections that add no information. ### When to override `@doc false` on `@impl` functions `@impl true` sets `@doc false` by default — the assumption is that the behaviour's `@callback` doc covers it. This is correct when the implementation is unremarkable. Override it with an explicit `@doc` when the implementation has semantics that would not be true of every conforming implementation. **The test:** "Would this statement be true of *any* implementation of this callback?" If yes → the doc belongs on the `@callback` in the behaviour, not here. If no → the implementation earns its own `@doc`. **Override when:** - The implementation makes a guarantee the behaviour doesn't promise (e.g., "always returns immediately", "never buffers", "fires at most once") - There's a race condition, ordering constraint, or subtle failure mode specific to this implementation - The implementation's behavior under edge cases differs from what the behaviour's generic contract implies **Don't override when:** - The doc would just restate the `@callback` doc in different words - The doc describes what the function does rather than what's surprising about this implementation - The function name + behaviour doc are sufficient for an implementor or caller to understand it **Example — unnecessary override (just restates the contract):** ```elixir defmodule JsonSerializer do @behaviour Serializer @doc """ Encodes the given term to a binary. """ @impl true def encode(term), do: Jason.encode!(term) end ``` **Example — justified override (implementation-specific guarantee):** ```elixir defmodule Immediate do @behaviour Aggregation @doc """ Always returns `{:ready, [signal]}` — immediate mode fires on first signal without buffering or waiting for a timer. """ @impl true def check(%Signal{} = signal), do: {:ready, [signal]} end ``` **Example — justified override (race condition warning):** ```elixir defmodule AlpacaAdapter do @behaviour BrokerAdapter @doc """ Cancels an open order by broker ID. Returns `:ok` on success. The order may still receive a final fill between the cancel request and confirmation — callers must handle the `partially_filled` → `cancelled` race. """ @impl true def cancel(credential, broker_order_id), do: ... end ``` **Why:** The behaviour's `@callback` doc is the generic contract — "what any implementation must do." An implementation's `@doc` is the specific contract — "what callers of *this* implementation can rely on." When those differ meaningfully, silence (`@doc false`) hides information that would prevent bugs. --- ## 11. Documentation with Link References (c: and t: prefixes) **Source:** `lib/elixir/lib/gen_server.ex` throughout the @moduledoc **What it does:** Uses ExDoc link syntax like `c:init/1` (callback reference), `t:server/0` (type reference), and backtick function references like `` `start_link/2` `` to create navigable cross-references. **Why:** Documentation is a hypertext. Cross-linking lets users navigate between related concepts. The `c:` and `t:` prefixes disambiguate between functions, callbacks, and types with the same name. **Anti-pattern:** Mentioning related functions/types by name without linking them, requiring users to manually search. **Code example:** ```elixir @moduledoc """ ... `c:init/1` transforms our initial argument to the initial state for the GenServer. `c:handle_call/3` fires when the server receives a synchronous `pop` message... Each call to `GenServer.call/3` results in a message that must be handled by the `c:handle_call/3` callback in the GenServer. ... """ ``` ### When to Use **Triggers:** - Your moduledoc or function doc references other functions, callbacks, or types in the same or other modules - Users would benefit from clicking through to related documentation - You have callbacks and functions with the same name that need disambiguation **Example — before:** ```elixir @doc """ Starts the server. Calls init/1 internally. See the server type for accepted values. """ ``` **Example — after:** ```elixir @doc """ Starts the server. Calls `c:init/1` internally. See `t:server/0` for accepted name values. """ ``` ### When NOT to Use **Don't use this when:** - You're writing documentation that won't be rendered by ExDoc (plain README, comments) - The reference is to a well-known Erlang/Elixir function that readers will recognize without a link - Over-linking makes the prose unreadable (every other word is a link) **Over-application example:** ```elixir @doc """ Returns `t:boolean/0` indicating if the `t:pid/0` from `Kernel.self/0` matches the `t:pid/0` stored in `t:state/0` field `:owner`. """ ``` **Better alternative:** ```elixir @doc """ Returns `true` if the calling process is the owner of this resource. """ ``` **Why:** Link references should aid navigation, not turn documentation into hypertext soup. Link types and callbacks that users might need to look up; don't link primitive types or universally known functions. ## Decision Tree - If the module is a primary entry point with 4+ public functions → use structured `@moduledoc` with sections (Pattern 1) - If a function has non-obvious behavior or edge cases → add `@doc` with sections and `## Examples` doctests (Pattern 2) - If adding a new public function to a versioned library → annotate with `@doc since: "X.Y.Z"` (Pattern 3) - If the function/macro is valid in guard clauses → add `@doc guard: true` metadata (Pattern 4) - If a function must be public for technical reasons but is not user-facing → use `@doc false` (Pattern 5) - If an entire module is purely internal implementation → use `@moduledoc false` (Pattern 6) - If documenting multi-component architecture (client-server, pipelines) → embed a Mermaid diagram (Pattern 7) - If critical information must stand out (security, breaking changes, `use` behavior) → use an admonition block (Pattern 8) - If a function still works but a better alternative exists → use `@doc deprecated:` for soft deprecation (Pattern 9) - If defining a behaviour callback with multiple return shapes → write comprehensive callback docs with trigger, params, returns, and example (Pattern 10)