# Elixir Code Standards Policy **Effective:** 2026-03-15 **Owner:** Engineering ## Purpose All Elixir code in TowerOps must be idiomatic, readable, and maintainable. This policy ensures consistency across the codebase and reduces cognitive load for contributors. ## Standards ### Pattern Matching Over Conditionals - Use pattern matching in function heads instead of `if`/`case` when branching on structure. - Prefer multi-clause functions over nested conditionals. ```elixir # Good def process(%{status: :active} = device), do: ... def process(%{status: :inactive} = device), do: ... # Avoid def process(device) do if device.status == :active do ... else ... end end ``` ### The Pipe Operator - Use `|>` for data transformation chains (3+ steps). - Never start a pipe with a raw value — assign or wrap it. - Each step in a pipe should take the piped value as its first argument. ### With Clauses - Use `with` for sequences of dependent operations that may fail. - Always include an `else` clause that handles expected error shapes. - Keep `with` chains under 5 clauses; extract helpers if longer. ### Naming - Modules: `PascalCase`, mirroring directory structure. - Functions: `snake_case`, verb-first (`fetch_device/1`, `parse_response/1`). - Private helpers: prefix with intent, not `do_` (e.g., `build_query` not `do_query`). - Boolean functions: end with `?` (e.g., `active?/1`). ### Contexts (Phoenix) - Business logic lives in context modules (`Towerops.Devices`, `Towerops.Alerts`). - LiveViews and controllers call contexts — never Repo directly. - Contexts return `{:ok, result}` or `{:error, reason}` tuples. - Keep context functions focused; one public function per use case. ### Error Handling - Use tagged tuples (`{:ok, _}` / `{:error, _}`) for expected failures. - Let unexpected failures crash — the supervisor will restart. - Never rescue broadly (`rescue e ->`) without re-raising or logging. - Use `Ecto.Multi` for operations that must be atomic. ### Documentation - All public functions must have `@doc` and `@spec`. - Modules must have `@moduledoc` describing purpose and responsibilities. - Internal/private functions: document with comments only when non-obvious. ### Testing - Every public context function must have tests. - Use `ExUnit.CaseTemplate` and factories for setup. - Async tests (`async: true`) unless they touch shared state (database). - Name tests descriptively: `"fetch_device/1 returns error when device not found"`. ### Formatting - Run `mix format` before every commit. CI will reject unformatted code. - Use `.formatter.exs` settings; do not override line length (98 chars). ### Dependencies - Minimize external dependencies. Prefer stdlib and OTP. - Every new dependency requires a brief justification in the PR description. - Pin versions explicitly in `mix.exs`. ## Enforcement - CI runs `mix format --check-formatted`, `mix credo --strict`, and `mix dialyzer`. - Code review required for all merges to `main`. - Violations block merge.