# Elixir Style
> Most of these guidelines are based on
> [The Elixir Style Guide](https://github.com/christopheradams/elixir_style_guide)
> by Christopher Adams, licensed under
> [CC-BY-3.0](https://creativecommons.org/licenses/by/3.0/).
## Formatting
### Whitespace
- Use blank lines between `def`s to break up a function into logical paragraphs.
For example:
```elixir
def some_function(some_data) do
some_data |> other_function() |> List.first()
end
def some_function do
result
end
def some_other_function do
another_result
end
def a_longer_function do
one
two
three
four
end
```
- If the function head and `do:` clause are too long to fit on the same line, put `do:` on a new line, indented one level more than the previous line. For example:
```elixir
def some_function([:foo, :bar, :baz] = args),
do: Enum.map(args, fn arg -> arg <> " is on a very long line!" end)
```
When the `do:` clause starts on its own line, treat it as a multiline function by separating it with blank lines.
```elixir
# not preferred
def some_function([]), do: :empty
def some_function(_),
do: :very_long_line_here
# preferred
def some_function([]), do: :empty
def some_function(_),
do: :very_long_line_here
```
- Add a blank line after a multiline assignment as a visual cue that the assignment is 'over'. For example:
```elixir
# not preferred
some_string =
"Hello"
|> String.downcase()
|> String.trim()
another_string <> some_string
# preferred
some_string =
"Hello"
|> String.downcase()
|> String.trim()
another_string <> some_string
```
```elixir
# also not preferred
something =
if x == 2 do
"Hi"
else
"Bye"
end
String.downcase(something)
# preferred
something =
if x == 2 do
"Hi"
else
"Bye"
end
String.downcase(something)
```
### Parentheses
- Use parentheses when defining a type.
```elixir
# not preferred
@type name :: atom
# preferred
@type name() :: atom
```
## General guidelines
The rules in this section may not be applied by the code formatter, but they are generally preferred practice.
### Expressions
- Keep single-line `def` clauses of the same function together, but separate multiline `def`s with a blank line. For example:
```elixir
def some_function(nil), do: {:error, "No Value"}
def some_function([]), do: :ok
def some_function([first | rest]) do
some_function(rest)
end
```
- If you have more than one multiline `def`, do not use single-line `def`s. For example:
```elixir
def some_function(nil) do
{:error, "No Value"}
end
def some_function([]) do
:ok
end
def some_function([first | rest]) do
some_function(rest)
end
def some_function([first | rest], opts) do
some_function(rest, opts)
end
```
- Use the pipe operator to chain functions together. For example:
```elixir
# not preferred
String.trim(String.downcase(some_string))
# preferred
some_string |> String.downcase() |> String.trim()
# Multiline pipelines are not further indented
some_string
|> String.downcase()
|> String.trim()
# Multiline pipelines on the right side of a pattern match
# should be indented on a new line
sanitized_string =
some_string
|> String.downcase()
|> String.trim()
```
- Avoid using the pipe operator just once, unless the first expression is a function. For example:
```elixir
# not preferred
some_string |> String.downcase()
# preferred
String.downcase(some_string)
# not preferred
Version.parse(System.version())
# preferred
System.version() |> Version.parse()
```
- Pipe directly into `case` and `if` instead of leaving a partial
pipeline inside the head of the control-flow expression. The
goal: never see `case foo |> bar() |> baz() do` — let the pipe
flow into the construct.
```elixir
# not preferred — partial pipe inside `case`
case partition |> Partition.current_table() |> :ets.lookup(key) do
[entry(value: value)] -> value
[] -> default
end
# preferred — pipe flows into `case`
partition
|> Partition.current_table()
|> :ets.lookup(key)
|> case do
[entry(value: value)] -> value
[] -> default
end
```
The same shape applies to `if`:
```elixir
value
|> normalize()
|> validate()
|> if do
handle_ok()
else
handle_error()
end
```
- Use parentheses when a `def` has arguments, and omit them when it doesn't. For example:
```elixir
# not preferred
def some_function arg1, arg2 do
# body omitted
end
def some_function() do
# body omitted
end
# preferred
def some_function(arg1, arg2) do
# body omitted
end
def some_function do
# body omitted
end
```
- Use `do:` for single-line `if/unless` statements.
```elixir
# preferred
if some_condition, do: # some_stuff
```
- Use `true` as the last condition of the `cond` special form when you need a clause that always matches.
```elixir
# not preferred
cond do
1 + 2 == 5 ->
"Nope"
1 + 3 == 5 ->
"Uh, uh"
:else ->
"OK"
end
# preferred
cond do
1 + 2 == 5 ->
"Nope"
1 + 3 == 5 ->
"Uh, uh"
true ->
"OK"
end
```
### Naming
- Use `snake_case` for atoms, functions and variables.
```elixir
# not preferred
:"some atom"
:SomeAtom
:someAtom
someVar = 5
def someFunction do
...
end
# preferred
:some_atom
some_var = 5
def some_function do
...
end
```
- Use `CamelCase` for modules (keep acronyms like HTTP, RFC, XML uppercase).
```elixir
# not preferred
defmodule Somemodule do
...
end
defmodule Some_Module do
...
end
defmodule SomeXml do
...
end
# preferred
defmodule SomeModule do
...
end
defmodule SomeXML do
...
end
```
- Functions that return a boolean (`true` or `false`) should be named with a trailing question mark.
```elixir
def cool?(var) do
String.contains?(var, "cool")
end
```
- Boolean checks that can be used in guard clauses (custom guards) should be named with an `is_` prefix.
```elixir
defguard is_cool(var) when var == "cool"
defguard is_very_cool(var) when var == "very cool"
```
### Comments
- Write expressive code and try to convey your program's intention through control-flow, structure and naming.
- Comments longer than a word are capitalized, and sentences use punctuation. Use one space after periods.
```elixir
# not preferred
# these lowercase comments are missing punctuation
# preferred
# Capitalization example
# Use punctuation for complete sentences.
```
- Limit comment lines to 80 characters.
#### Comment Annotations
- Annotations should usually be written on the line immediately above the relevant code.
- The annotation keyword is uppercase, and is followed by a colon and a space, then a note describing the problem.
```elixir
# TODO: Deprecate in v1.5.
def some_function(arg), do: {:ok, arg}
```
- In cases where the problem is so obvious that any documentation would be redundant, annotations may be left with no note. This usage should be the exception and not the rule.
```elixir
start_task()
# FIXME
Process.sleep(5000)
```
- Use `TODO` to note missing features or functionality that should be added at a later date.
- Use `FIXME` to note broken code that needs to be fixed.
- Use `OPTIMIZE` to note slow or inefficient code that may cause performance problems.
- Use `HACK` to note code smells where questionable coding practices were used and should be refactored away.
- Use `REVIEW` to note anything that should be looked at to confirm it is working as intended. For example: `REVIEW: Are we sure this is how the client does X currently?`
- Use other custom annotation keywords if it feels appropriate, but be sure to document them in your project's `README` or similar.
### Comment Constants
- When defining a constant, pick a descriptive name that reflects the intention or usage of the constant and add a comment with a short description.
**Not preferred:**
```elixir
@retries 10
```
**Preferred:**
```elixir
# Default HTTP retries
@http_retries 10
```
- When the constant is a timeout in milliseconds, use `:timer` module instead of explicit value (e.g., `:timer.seconds/1`, `:timer.minutes/1`, `:timer.hours/1`).
**Not preferred:**
```elixir
# Default HTTP request timeout in milliseconds
@http_request_timeout 10_000
```
**Preferred:**
```elixir
# Default HTTP request timeout in milliseconds
@http_request_timeout :timer.seconds(10)
```
- When the constant is a list of atoms or strings, a regex, or anything that can be expressed using Sigils, then use Sigils.
**Not preferred:**
```elixir
# User types
@user_types [:admin, :editor, :customer]
# Supported country codes
@user_types ["US", "ES", "CO"]
```
**Preferred:**
```elixir
# User types
@user_types ~w(admin editor customer)a
# Supported country codes
@user_types ~w(US ES CO)
```
### Modules
- List module attributes, directives, and macros in the following order:
1. `@moduledoc`
2. `@behaviour`
3. `use`
4. `import`
5. `require`
6. `alias`
7. `@module_attribute`
8. `defstruct`
9. `@type`
10. `@callback`
11. `@macrocallback`
12. `@optional_callbacks`
13. `defmacro`, `defmodule`, `defguard`, `def`, etc.
Add a blank line between each grouping, and sort the terms (like module names) alphabetically. Here's an overall example of how you should order things in your modules:
```elixir
defmodule MyModule do
@moduledoc """
An example module
"""
@behaviour MyBehaviour
use GenServer
import Something
import SomethingElse
require Integer
alias My.Long.Module.Name
alias My.Other.Module.Example
@module_attribute :foo
@other_attribute 100
defstruct [:name, params: []]
@type params :: [{binary, binary}]
@callback some_function(term) :: :ok | {:error, term}
@macrocallback macro_name(term) :: Macro.t()
@optional_callbacks macro_name: 1
@doc false
defmacro __using__(_opts), do: :no_op
@doc """
Determines when a term is `:ok`. Allowed in guards.
"""
defguard is_ok(term) when term == :ok
@impl true
def init(state), do: {:ok, state}
# Define other functions here.
end
```
- Use the `__MODULE__` pseudo variable when a module refers to itself. This avoids having to update any self-references when the module name changes.
```elixir
defmodule SomeProject.SomeModule do
defstruct [:name]
def name(%__MODULE__{name: name}), do: name
end
```
### Typespecs
- Place `@typedoc` and `@type` definitions together, and separate each pair with a blank line.
```elixir
defmodule SomeModule do
@moduledoc false
@typedoc "The name"
@type name() :: atom()
@typedoc "The result"
@type result() :: {:ok, any()} | {:error, any()}
...
end
```
- Name the main type for a module `t()`, for example: the type specification for a struct.
```elixir
defstruct name: nil, params: []
@typedoc "The type for ..."
@type t() :: %__MODULE__{
name: String.t() | nil,
params: Keyword.t()
}
```
- Place specifications right before the function definition, after the `@doc`, without separating them by a blank line.
```elixir
@doc """
Some function description.
"""
@spec some_function(any()) :: result()
def some_function(some_data) do
{:ok, some_data}
end
```
### Structs
- Use a list of atoms for struct fields that default to `nil`, followed by the other keywords.
```elixir
# not preferred
defstruct name: nil, params: nil, active: true
# preferred
defstruct [:name, :params, active: true]
```
- Omit square brackets when the argument of a `defstruct` is a keyword list.
```elixir
# not preferred
defstruct [params: [], active: true]
# preferred
defstruct params: [], active: true
# required - brackets are not optional, with at least one atom in the list
defstruct [:name, params: [], active: true]
```
- If a struct definition spans multiple lines, put each element on its own line, keeping the elements aligned.
```elixir
defstruct foo: "test",
bar: true,
baz: false,
qux: false,
quux: 1
```
If a multiline struct requires brackets, format it as a multiline list:
```elixir
defstruct [
:name,
params: [],
active: true
]
```
### Exceptions
- Make exception names end with a trailing `Error`.
```elixir
# not preferred
defmodule BadHTTPCode do
defexception [:message]
end
defmodule BadHTTPCodeException do
defexception [:message]
end
# preferred
defmodule BadHTTPCodeError do
defexception [:message]
end
```
- Use lowercase error messages when raising exceptions, with no trailing punctuation.
```elixir
# not preferred
raise ArgumentError, "This is not valid."
# preferred
raise ArgumentError, "this is not valid"
```
### Collections
- Always use the special syntax for keyword lists.
```elixir
# not preferred
some_value = [{:a, "baz"}, {:b, "qux"}]
# preferred
some_value = [a: "baz", b: "qux"]
```
- Use the shorthand key-value syntax for maps when all of the keys are atoms.
```elixir
# not preferred
%{:a => 1, :b => 2, :c => 0}
# preferred
%{a: 1, b: 2, c: 3}
```
- Use the verbose key-value syntax for maps if any key is not an atom.
```elixir
# not preferred
%{"c" => 0, a: 1, b: 2}
# preferred
%{:a => 1, :b => 2, "c" => 0}
```
### Testing
- When writing ExUnit assertions, put the expression being tested to the left of the operator, and the expected result to the right, unless the assertion is a pattern match.
```elixir
# not preferred
assert true == actual_function(1)
# preferred
assert actual_function(1) == true
# required - the assertion is a pattern match, and the `expected` variable is used later
assert {:ok, expected} = actual_function(3)
assert expected.atom == :atom
assert expected.int == 123
# preferred - if the right side is known, even if it is a tuple
assert actual_function(11) == {:ok, %{atom: :atom, int: 123}}
# preferred - if the right side is known (using a variable)
expected = %{atom: :atom, int: 123}
assert actual_function(11) == {:ok, expected}
```
## Extra guidelines
- Use a blank line for the return or final statement (unless it is a single line).
**Avoid**:
def some_function(arg) do
Logger.info("Arg: #{inspect(some_data)}")
:ok
end
**Prefer**:
def some_function(some_data) do
Logger.info("Arg: #{inspect(some_data)}")
:ok
end
- Use multi-line when a function returns with a pipe.
**Avoid**:
def some_function(some_data) do
some_data |> other_function() |> List.first()
end
**Prefer**:
def some_function(some_data) do
some_data
|> other_function()
|> List.first()
end
- Use `with` when only one case has to be handled, either the success or the error.
**Avoid**: `case` forwarding the same result
case some_call() do
:ok ->
:ok
{:error, reason} = error ->
Logger.error("Error: #{inspect(reason)}")
error
end
**Prefer**: `with` handling only the needed case
with {:error, reason} = error <- some_call() do
Logger.error("Error: #{inspect(reason)}")
error
end