lexmag/elixir-style-guide


An opinionated Elixir style guide

Keywords: elixir


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

Source Code Layout

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

  • Use one expression per line, as a corollary - don't use semicolon ; to separate statements and expressions. [link]

  • Use spaces around binary operators, after commas ,, colons : and semicolons ;. Do not put spaces around matched pairs like brackets [], braces {}, etc. Whitespace might be (mostly) irrelevant to the Elixir compiler, but its proper use is the key to writing easily readable code. [link]

    sum = 1   2
    [first | rest] = 'three'
    {a1, a2} = {2, 3}
    Enum.join(["one", <<"two">>, sum])
  • No spaces after unary operators and inside range literals, the only exception is the not operator. [link]

    angle = -45
    ^result = Float.parse("42.01")
    2 in 1..5
    not File.exists?(path)
  • Use spaces around default arguments \\ definition. [link]

  • 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>>
  • Indent when guard clauses as much as the function definitions they apply to. [link]

    def format_error({exception, stacktrace})
    when is_list(stacktrace) and stacktrace != [] do
      #...
    end
  • When assigning the result of a multi-line expression, do not preserve alignment of its parts. [link]

    # Bad
    {found, not_found} = Enum.map(files, &Path.expand(&1, path))
                         |> Enum.partition(&File.exists?/1)
    
    prefix = case base do
               :binary -> "0b"
               :octal  -> "0o"
               :hex    -> "0x"
             end
    
    # Good
    {found, not_found} =
      Enum.map(files, &Path.expand(&1, path))
      |> Enum.partition(&File.exists?/1)
    
    prefix = case base do
      :binary -> "0b"
      :octal  -> "0o"
      :hex    -> "0x"
    end
  • Add underscores to large numeric literals to improve their readability. [link]

    num = 1_000_000
  • Avoid trailing whitespaces. [link]

  • End each file with a newline. [link]

Syntax

  • Always use parentheses around def arguments, don't omit them even when a function has no arguments. [link]

    # Bad
    def main arg1, arg2 do
      #...
    end
    
    def main do
      #...
    end
    
    # Good
    def main(arg1, arg2) do
      #...
    end
    
    def main() do
      #...
    end
  • Parentheses are a must for local zero-arity function calls and definitions. [link]

    # Bad
    pid = self
    def new, do: %MapSet{}
    
    # Good
    pid = self()
    def new(), do: %MapSet{}
    config = IEx.Config.new

    The same applies to local one-arity function calls in pipelines.

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

    # Bad
    String.downcase(String.strip(input))
    
    # Good
    input |> String.strip |> String.downcase
    String.strip(input) |> String.downcase

    Use a single level of indentation for multi-line pipelines.

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

    Avoid needless pipelines like the plague. [link]

    # Bad
    result = input |> String.strip
    
    # Good
    result = String.strip(input)
  • When making a multi-line expression, keep binary operators (the only exception is the |> operator) at the ends of the lines. [link]

    # Bad
    "No matching message.\n"
    <> "Process mailbox:\n"
    <> mailbox
    
    # Good
    "No matching message.\n" <>
    "Process mailbox:\n" <>
    mailbox
  • Never use unless with else. Rewrite these with the positive case first. [link]

    # Bad
    unless Enum.empty?(coll) do
      :ok
    else
      :error
    end
    
    # Good
    if Enum.empty?(coll) do
      :error
    else
      :ok
    end
  • Omit else option in if and unless clauses if it returns nil. [link]

    # Bad
    if byte_size(data) > 0, do: data, else: nil
    
    # Good
    if byte_size(data) > 0, do: data
  • Always use true as the last condition of a cond statement. [link]

    # Bad
    cond do
      char in ?0..?9 -> char - ?0
      char in ?A..?Z -> char - ?A   10
      :other         -> char - ?a   10
    end
    
    # Good
    cond do
      char in ?0..?9 -> char - ?0
      char in ?A..?Z -> char - ?A   10
      true           -> char - ?a   10
    end
  • Never use ||, && and ! for strictly boolean checks. Use these operators only if any of the arguments are non-boolean. [link]

    # Bad
    is_atom(name) && name != nil
    is_binary(task) || is_atom(task)
    
    # Good
    is_atom(name) and name != nil
    is_binary(task) or is_atom(task)
    line && line != 0
    file || "sample.exs"
  • Favor the binary concatenation operator <> over bitstring syntax for patterns matching binaries. [link]

    # Bad
    <<"http://", _rest::bytes>> = input
    <<first::utf8, rest::bytes>> = input
    
    # Good
    "http://" <> _rest = input
    <<first::utf8>> <> rest = input
  • Use uppercase in definition of hex literals. [link]

    # Bad
    <<0xef, 0xbb, 0xbf>>
    
    # Good
    <<0xEF, 0xBB, 0xBF>>

Naming

  • Use snake_case for atoms, functions, variables and module attributes. [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. [link]

    # Bad
    defmodule :appStack do
      #...
    end
    
    defmodule App_Stack do
      #...
    end
    
    defmodule Appstack do
      #...
    end
    
    # Good
    defmodule AppStack do
      #...
    end
  • The names of predicate functions (a function that return a boolean value) should have a trailing question mark ? rather than a leading has_ or similar. [link]

    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, e.g. 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

  • Write self-documenting code and ignore the rest of this section. Seriously! [link]

  • Use one space between the leading # character of the comment and the text of the comment. [link]

  • Avoid superfluous comments. [link]

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

Modules

  • Use a consistent structure in module definitions. [link]

    @compile :inline_list_funcs
    
    use GenServer
    
    require Logger
    
    alias Kernel.Typespec
    
    import Bitwise
  • Use __MODULE__ pseudo variable to reference 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 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]

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"

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.

Project Statistics

Sourcerank 6
Repository Size 47.9 KB
Stars 351
Forks 24
Watchers 27
Open issues 0
Dependencies 0
Contributors 2
Tags 0
Created
Last updated
Last pushed

Top Contributors See all

Aleksei Magusev Damir Gainetdinov

Interesting Forks See all

josevalim/elixir-style-guide
An opinionated Elixir style guide
Last pushed - 4 stars
uohzxela/elixir-style-guide
An opinionated Elixir style guide
Last pushed - 1 stars

Something wrong with this page? Make a suggestion

Last synced: 2017-11-24 18:48:30 UTC

Login to resync this repository