elixir-style-guide

by lexmag

An opinionated Elixir style guide

442 Stars 30 Forks Last release: Not found 46 Commits 0 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

Elixir Style Guide

A programmer does not primarily write code; rather, he primarily writes to another programmer about his problem solution. The understanding of this fact is the final step in his maturation as technician.

What a Programmer Does, 1967

Table of Contents

The following section are automatically applied by the code formatter in Elixir v1.6 and listed here only for documentation purposes:

Linting

  • Favor the pipeline operator
    |>
    to chain function calls together. [link]
  # Bad
  String.downcase(String.strip(input))

Good

input |> String.strip() |> String.downcase()

For a multi-line pipeline, place each function call on a new line, and retain the level of indentation.

  input
  |> String.strip()
  |> String.downcase()
  |> String.slice(1, 3)

  • Avoid needless pipelines like the plague. [link]

    # Bad
    result = input |> String.strip()
    
    # Good
    result = String.strip(input)
    </pre>
    <ul>
    <li>
    <a name="anonymous-pipeline"></a>
    Don't use anonymous functions in pipelines.
    <sup>[<a href="https://github.com/lexmag/elixir-style-guide/blob/master/#anonymous-pipeline">link</a>]</sup>
    </li>
    </ul>
    <pre class="language-elixir">  # Bad
    sentence
    |&gt; String.split(~r/\s/)
    |&gt; (fn words -&gt; [@sentence_start | words] end).()
    |&gt; Enum.join(" ")
    
    # Good
    split_sentence = String.split(sentence, ~r/\s/)
    Enum.join([@sentence_start | split_sentence], " ")
    </pre>
    <p>Consider defining private helper function when appropriate:</p>
    <pre class="language-elixir">  # Good
    sentence
    |&gt; String.split(~r/\s/)
    |&gt; prepend(@sentence_start)
    |&gt; Enum.join(" ")
    </pre>
    <ul>
    <li>
    <a name="no-else-with-unless"></a>
    Never use <pre>unless</pre> with <pre>else</pre>. Rewrite these with the positive case first.
    <sup>[<a href="https://github.com/lexmag/elixir-style-guide/blob/master/#no-else-with-unless">link</a>]</sup>
    </li>
    </ul>
    <pre class="language-elixir">  # Bad
    unless Enum.empty?(coll) do
      :ok
    else
      :error
    end
    
    # Good
    if Enum.empty?(coll) do
      :error
    else
      :ok
    end
    </pre>
    <ul>
    <li>
    <a name="no-nil-else"></a>
    Omit the <pre>else</pre> option in <pre>if</pre> and <pre>unless</pre> constructs if <pre>else</pre> returns <pre>nil</pre>.
    <sup>[<a href="https://github.com/lexmag/elixir-style-guide/blob/master/#no-nil-else">link</a>]</sup>
    </li>
    </ul>
    <pre class="language-elixir">  # Bad
    if byte_size(data) &gt; 0, do: data, else: nil
    
    # Good
    if byte_size(data) &gt; 0, do: data
    </pre>
    <ul>
    <li>
    <a name="true-in-cond"></a>
    If you have an always-matching clause in the <pre>cond</pre> special form, use <pre>true</pre> as its condition.
    <sup>[<a href="https://github.com/lexmag/elixir-style-guide/blob/master/#true-in-cond">link</a>]</sup>
    </li>
    </ul>
    <pre class="language-elixir">  # Bad
    cond do
      char in ?0..?9 -&gt;
        char - ?0
    
      char in ?A..?Z -&gt;
        char - ?A + 10
    
      :other -&gt;
        char - ?a + 10
    end
    
    # Good
    cond do
      char in ?0..?9 -&gt;
        char - ?0
    
      char in ?A..?Z -&gt;
        char - ?A + 10
    
      true -&gt;
        char - ?a + 10
    end
    </pre>
    <ul>
    <li>
    <a name="boolean-operators"></a>
    Never use <pre>||</pre>, <pre>&amp;&amp;</pre>, and <pre>!</pre> for strictly boolean checks. Use these operators only if any of the arguments are non-boolean.
    <sup>[<a href="https://github.com/lexmag/elixir-style-guide/blob/master/#boolean-operators">link</a>]</sup>
    </li>
    </ul>
    <pre class="language-elixir">  # Bad
    is_atom(name) &amp;&amp; name != nil
    is_binary(task) || is_atom(task)
    
    # Good
    is_atom(name) and name != nil
    is_binary(task) or is_atom(task)
    line &amp;&amp; line != 0
    file || "sample.exs"
    </pre>
    <ul>
    <li>
    <a name="patterns-matching-binaries"></a>
    Favor the binary concatenation operator <pre>&lt;&gt;</pre> over bitstring syntax for patterns matching binaries.
    <sup>[<a href="https://github.com/lexmag/elixir-style-guide/blob/master/#patterns-matching-binaries">link</a>]</sup>
    </li>
    </ul>
    <pre class="language-elixir">  # Bad
    &lt;&gt; = input
    &lt;<:utf8 rest::bytes>&gt; = input
    
    # Good
    "http://" &lt;&gt; _rest = input
    &lt;<:utf8>&gt; &lt;&gt; rest = input
    </:utf8></:utf8></pre>
    <h3>Naming</h3>
    
  • Use
    snake_case
    for functions, variables, module attributes, and atoms. [link]
  # Bad
  :"no match"
  :Error
  :badReturn

fileName = "sample.txt"

@_VERSION "0.0.1"

def readFile(path) do # ... end

Good

:no_match :error :bad_return

file_name = "sample.txt"

@version "0.0.1"

def read_file(path) do # ... end

  • Use
    CamelCase
    for module names. Keep uppercase acronyms as uppercase. [link]
  # Bad
  defmodule :appStack do
    # ...
  end

defmodule App_Stack do # ... end

defmodule Appstack do # ... end

defmodule Html do # ... end

Good

defmodule AppStack do # ... end

defmodule HTML do # ... end

  • The names of predicate functions (functions that return a boolean value) should have a trailing question mark
    ?
    rather than a leading
    has_
    or similar. [link]
  # Bad
  def is_leap(year) do
    # ...
  end

Good

def leap?(year) do # ... end

Always use a leading

is_
when naming guard-safe predicate macros.
  defmacro is_date(month, day) do
    # ...
  end
  • Use

    snake_case
    for naming directories and files, for example
    lib/my_app/task_server.ex
    . [link]
  • Avoid using one-letter variable names. [link]

Comments

Remember, good code is like a good joke: It needs no explanation.

Russ Olsen

  • Use code comments only to communicate important details to another person reading the code. For example, a high-level description of the algorithm being implemented or why certain critical decisions, such as optimization or business rules, were made. [link]

  • Avoid superfluous comments. [link]

  # Bad
  String.first(input) # Get first grapheme.

Modules

  • Use a consistent structure when calling
    use
    /
    import
    /
    alias
    /
    require
    : call them in this order and group multiple calls to each of them. [link]
  use GenServer

import Bitwise import Kernel, except: [length: 1]

alias Mix.Utils alias MapSet, as: Set

require Logger

  • Use the
    __MODULE__
    pseudo-variable to reference the current module. [link]
  # Bad
  :ets.new(Kernel.LexicalTracker, [:named_table])
  GenServer.start_link(Module.LocalsTracker, nil, [])

Good

:ets.new(MODULE, [:named_table]) GenServer.start_link(MODULE, nil, [])

Regular Expressions

  • Regular expressions are the last resort. Pattern matching and the
    String
    module are things to start with. [link]
  # Bad
  Regex.run(~r/#(\d{2})(\d{2})(\d{2})/, color)
  Regex.match?(~r/(email|password)/, input)

Good

<#, p1::2-bytes, p2::2-bytes, p3::2-bytes>> = color String.contains?(input, ["email", "password"])

  • Use non-capturing groups when you don't use the captured result. [link]
  ~r/(?:post|zip )code: (\d+)/
  • Be careful with
    ^
    and
    $
    as they match start and end of the line respectively. If you want to match the whole string use:
    \A
    and
    \z
    (not to be confused with
    \Z
    which is the equivalent of
    \n?\z
    ). [link]

Structs

  • When calling
    defstruct/1
    , don't explicitly specify
    nil
    for fields that default to
    nil
    . [link]
  # Bad
  defstruct first_name: nil, last_name: nil, admin?: false

Good

defstruct [:first_name, :last_name, admin?: false]

Exceptions

  • Make exception names end with a trailing
    Error
    . [link]
  # Bad
  BadResponse
  ResponseException

Good

ResponseError

  • Use non-capitalized error messages when raising exceptions, with no trailing punctuation. [link]
  # Bad
  raise ArgumentError, "Malformed payload."

Good

raise ArgumentError, "malformed payload"

There is one exception to the rule - always capitalize Mix error messages.

  Mix.raise("Could not find dependency")

ExUnit

  • When asserting (or refuting) something with comparison operators (such as
    ==
    ,
    <
    ,
    >=
    , and similar), put the expression being tested on the left-hand side of the operator and the value you're testing against on the right-hand side. [link]
  # Bad
  assert "héllo" == Atom.to_string(:"héllo")

Good

assert Atom.to_string(:"héllo") == "héllo"

When using the match operator

=
, put the pattern on the left-hand side (as it won't work otherwise).
  assert {:error, _reason} = File.stat("./non_existent_file")

Formatting

The rules below are automatically applied by the code formatter in Elixir v1.6. They are provided here for documentation purposes and for those maintaining older codebases.

Whitespace

Whitespace might be (mostly) irrelevant to the Elixir compiler, but its proper use is the key to writing easily readable code.

  • Avoid trailing whitespaces. [link]

  • End each file with a newline. [link]

  • Use two spaces per indentation level. No hard tabs. [link]

  # Bad
  def register_attribute(name, opts) do
      register_attribute(__MODULE__, name, opts)
  end

Good

def register_attribute(name, opts) do register_attribute(MODULE, name, opts) end

  • Use a space before and after binary operators. Use a space after commas
    ,
    , colons
    :
    , and semicolons
    ;
    . Do not put spaces around matched pairs like brackets
    []
    , braces
    {}
    , and so on. [link]
  # Bad
  sum = 1+1
  [first|rest] = 'three'
  {a1,a2} = {2 ,3}
  Enum.join( [ "one" , << "two" >>, sum ])

Good

sum = 1 + 2 [first | rest] = 'three' {a1, a2} = {2, 3} Enum.join(["one", <>, sum])

  • Use no spaces after unary operators and inside range literals. The only exception is the
    not
    operator: use a space after it. [link]
  # Bad
  angle = - 45
  ^ result = Float.parse("42.01")

Good

angle = -45 ^result = Float.parse("42.01") 2 in 1..5 not File.exists?(path)

  • Use spaces around default arguments
    \\
    definition. [link]
  # Bad
  def start_link(fun, options\\[])

Good

def start_link(fun, options \ [])

  • Do not put spaces around segment options definition in bitstrings. [link]
  # Bad
  <<102 :: unsigned-big-integer, rest :: binary>>
  <<102::unsigned - big - integer, rest::binary>>

Good

<<102::unsigned-big-integer, rest::binary>>

  • Use one space between the leading
    #
    character of the comment and the text of the comment. [link]
  # Bad
  #Amount to take is greater than the number of elements

Good

Amount to take is greater than the number of elements

  • Always use a space before
    ->
    in 0-arity anonymous functions. [link]
  # Bad
  Task.async(fn->
    ExUnit.Diff.script(left, right)
  end)

Good

Task.async(fn -> ExUnit.Diff.script(left, right) end)

Indentation

  • Indent the right-hand side of a binary operator one level more than the left-hand side if left-hand side and right-hand side are on different lines. The only exceptions are
    when
    in guards and
    |>
    , which go on the beginning of the line and should be indented at the same level as their left-hand side. Do this also for binary operators when assigning. [link]
  # Bad

"No matching message.\n" <> "Process mailbox:\n" <> mailbox

message = "No matching message.\n" <> "Process mailbox:\n" <> mailbox

input |> String.strip() |> String.downcase()

defp valid_identifier_char?(char) when char in ?a..?z when char in ?A..?Z when char in ?0..?9 when char == ?_ do true end

defp parenless_capture?({op, _meta, _args}) when is_atom(op) and atom not in @unary_ops and atom not in @binary_ops do true end

Good

"No matching message.\n" <> "Process mailbox:\n" <> mailbox

message = "No matching message.\n" <> "Process mailbox:\n" <> mailbox

input |> String.strip() |> String.downcase()

defp valid_identifier_char?(char) when char in ?a..?z when char in ?A..?Z when char in ?0..?9 when char == ?_ do true end

defp parenless_capture?({op, _meta, _args}) when is_atom(op) and atom not in @unary_ops and atom not in @binary_ops do true end

  • Use the indentation shown below for the
    with
    special form: [link]
  with {year, ""} 
      {:error, :invalid_format}
  end

Always use the indentation above if there's an

else
option. If there isn't, the following indentation works as well:
  with {:ok, date} 
  • Use the indentation shown below for the
    for
    special form: [link]
  for {alias, _module} 

If the body of the

do
block is short, the following indentation works as well:
  for partition 
  • Avoid aligning expression groups: [link]
  # Bad
  module = env.module
  arity  = length(args)

def inspect(false), do: "false" def inspect(true), do: "true" def inspect(nil), do: "nil"

Good

module = env.module arity = length(args)

def inspect(false), do: "false" def inspect(true), do: "true" def inspect(nil), do: "nil"

The same non-alignment rule applies to

 and 
->
clauses as well.

  • Use a single level of indentation for multi-line pipelines. [link]
  input
  |> String.strip()
  |> String.downcase()
  |> String.slice(1, 3)

Term representation

  • Add underscores to decimal literals that have six or more digits. [link]
  # Bad
  num = 1000000
  num = 1_500

Good

num = 1_000_000 num = 1500

  • Use uppercase letters when using hex literals. [link]
  # Bad
  <<0xef, 0xbb, 0xbf>>

Good

<<0xEF, 0xBB, 0xBF>>

  • When using atom literals that need to be quoted because they contain characters that are invalid in atoms (such as
    :"foo-bar"
    ), use double quotes around the atom name: [link]
  # Bad
  :'foo-bar'
  :'atom number #{index}'

Good

:"foo-bar" :"atom number #{index}"

  • When dealing with lists, maps, structs, or tuples whose elements span over multiple lines and are on separate lines with regard to the enclosing brackets, it's advised to not use a trailing comma on the last element: [link]
  [
    :foo,
    :bar,
    :baz
  ]

Parentheses

  • Parentheses are a must for local or imported zero-arity function calls. [link]
  # Bad
  pid = self
  import System, only: [schedulers_online: 0]
  schedulers_online

Good

pid = self() import System, only: [schedulers_online: 0] schedulers_online()

The same should be done for remote zero-arity function calls:

  # Bad
  Mix.env

Good

Mix.env()

This rule also applies to one-arity function calls (both local and remote) in pipelines:

  # Bad
  input
  |> String.strip
  |> decode

Good

input |> String.strip() |> decode()

  • Never wrap the arguments of anonymous functions in parentheses. [link]
  # Bad
  Agent.get(pid, fn(state) -> state end)
  Enum.reduce(numbers, fn(number, acc) ->
    acc + number
  end)

Good

Agent.get(pid, fn state -> state end) Enum.reduce(numbers, fn number, acc -> acc + number end)

  • Always use parentheses around arguments to definitions (such as
    def
    ,
    defp
    ,
    defmacro
    ,
    defmacrop
    ,
    defdelegate
    ). Don't omit them even when a function has no arguments. [link]
  # Bad
  def main arg1, arg2 do
    # ...
  end

defmacro env do # ... end

Good

def main(arg1, arg2) do # ... end

defmacro env() do # ... end

  • Always use parens on zero-arity types. [link]
  # Bad
  @spec start_link(module, term, Keyword.t) :: on_start

Good

@spec start_link(module(), term(), Keyword.t()) :: on_start()

Layout

  • Use one expression per line. Don't use semicolons (
    ;
    ) to separate statements and expressions. [link]
  # Bad
  stacktrace = System.stacktrace(); fun.(stacktrace)

Good

stacktrace = System.stacktrace() fun.(stacktrace)

  • When assigning the result of a multi-line expression, begin the expression on a new line. [link]
  # Bad
  {found, not_found} = files
                       |> Enum.map(&Path.expand(&1, path))
                       |> Enum.partition(&File.exists?/1)

prefix = case base do :binary -> "0b" :octal -> "0o" :hex -> "0x" end

Good

{found, not_found} = files |> Enum.map(&Path.expand(&1, path)) |> Enum.partition(&File.exists?/1)

prefix = case base do :binary -> "0b" :octal -> "0o" :hex -> "0x" end

  • When writing a multi-line expression, keep binary operators at the end of each line. The only exception is the
    |>
    operator (which goes at the beginning of the line). [link]
  # Bad

"No matching message.\n" <> "Process mailbox:\n" <> mailbox

input |> String.strip() |> decode()

Good

"No matching message.\n" <> "Process mailbox:\n" <> mailbox

input |> String.strip() |> decode()

License

This work was created by Aleksei Magusev and is licensed under the CC BY 4.0 license.

Creative Commons License

Credits

The structure of the guide and some points that are applicable to Elixir were taken from the community-driven Ruby coding style guide.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.