Skip to content

gaynetdinov/elixir-style-guide

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

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 guard when 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 the 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 calls. [link]

    # Bad
    pid = self
    
    # Good
    pid = self()
  • 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)
  • 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

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.

About

An opinionated Elixir style guide

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published