elixir_style_guide

by christopheradams

christopheradams / elixir_style_guide

A community driven style guide for Elixir

3.5K Stars 269 Forks Last release: over 3 years ago (v2017-03-14) 289 Commits 2 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:

The Elixir Style Guide

Table of Contents

Prelude

Liquid architecture. It's like jazz — you improvise, you work together, you play off each other, you make something, they make something.

—Frank Gehry

Style matters. Elixir has plenty of style but like all languages it can be stifled. Don't stifle the style.

About

This is community style guide for the Elixir programming language. Please feel free to make pull requests and suggestions, and be a part of Elixir's vibrant community.

If you're looking for other projects to contribute to please see the Hex package manager site.

Translations of the guide are available in the following languages:

Formatting

Elixir v1.6 introduced a Code Formatter and Mix format task. The formatter should be preferred for all new projects and source code.

The rules in this section are applied automatically by the code formatter, but are provided here as examples of the preferred style.

Whitespace

  • Avoid trailing whitespace. [link]

  • End each file with a newline. [link]

  • Use Unix-style line endings (*BSD/Solaris/Linux/OSX users are covered by default, Windows users have to be extra careful). [link]

  • If you're using Git you might want to add the following configuration setting to protect your project from Windows line endings creeping in: [link]

  git config --global core.autocrlf true
  • Limit lines to 98 characters. Otherwise, set the

    :line_length
    option in your
    .formatter.exs
    file. [link]
  • Use spaces around operators, after commas, colons and semicolons. Do not put spaces around matched pairs like brackets, parentheses, etc. Whitespace might be (mostly) irrelevant to the Elixir runtime, but its proper use is the key to writing easily readable code. [link]

  sum = 1 + 2
  {a, b} = {2, 3}
  [first | rest] = [1, 2, 3]
  Enum.map(["one", <>, "three"], fn num -> IO.puts(num) end)
  • Do not use spaces after non-word operators that only take one argument; or around the range operator. [link]
  0 - 1 == -1
  ^pinned = some_func()
  5 in 1..10
  • Use blank lines between
    def
    s to break up a function into logical paragraphs. [link]
  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

  • Don't put a blank line after

    defmodule
    . [link]
  • 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. [link]
  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.
  # 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'. [link]
  # 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

  # 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)

  • If a list, map, or struct spans multiple lines, put each element, as well as the opening and closing brackets, on its own line. Indent each element one level, but not the brackets. [link]
  # not preferred
  [:first_item, :second_item, :next_item,
  :final_item]

preferred

[ :first_item, :second_item, :next_item, :final_item ]

  • When assigning a list, map, or struct, keep the opening bracket on the same line as the assignment. [link]
  # not preferred
  list =
  [
    :first_item,
    :second_item
  ]

preferred

list = [ :first_item, :second_item ]

  • If any
    case
    or
    cond
    clause needs more than one line (due to line length, multiple expressions in the clause body, etc.), use multi-line syntax for all clauses, and separate each one with a blank line. [link]
  # not preferred
  case arg do
    true -> IO.puts("ok"); :ok
    false -> :error
  end

not preferred

case arg do true -> IO.puts("ok") :ok false -> :error end

preferred

case arg do true -> IO.puts("ok") :ok

false -&gt;
  :error

end

  • Place comments above the line they comment on. [link]
  String.first(some_string) # not preferred

preferred

String.first(some_string)

  • Use one space between the leading
    #
    character of the comment and the text of the comment. [link]
  #not preferred
  String.first(some_string)

preferred

String.first(some_string)

Indentation

  • Indent and align successive
    with
    clauses. Put the
    do:
    argument on a new line, aligned with the previous clauses. [link]
  with {:ok, foo} 
  • If the
    with
    expression has a
    do
    block with more than one line, or has an
    else
    option, use multiline syntax. [link]
  with {:ok, foo} 
      {:error, :bad_arg}
  end

Parentheses

  • Use parentheses for one-arity functions when using the pipe operator (
    |>
    ). [link]
  # not preferred
  some_string |> String.downcase |> String.trim

preferred

some_string |> String.downcase() |> String.trim()

  • Never put a space between a function name and the opening parenthesis. [link]
  # not preferred
  f (3 + 2)

preferred

f(3 + 2)

  • Use parentheses in function calls, especially inside a pipeline. [link]
  # not preferred
  f 3

preferred

f(3)

not preferred and parses as rem(2, (3 |> g)), which is not what you want.

2 |> rem 3 |> g

preferred

2 |> rem(3) |> g

  • Omit square brackets from keyword lists whenever they are optional. [link]
  # not preferred
  some_function(foo, bar, [a: "baz", b: "qux"])

preferred

some_function(foo, bar, a: "baz", b: "qux")

The Guide

The rules in this section may not be applied by the code formatter, but they are generally preferred practice.

Expressions

  • Run single-line
    def
    s that match for the same function together, but separate multiline
    def
    s with a blank line. [link]
  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. [link]
  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. [link]
  # 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()

While this is the preferred method, take into account that copy-pasting multiline pipelines into IEx might result in a syntax error, as IEx will evaluate the first line without realizing that the next line has a pipeline. To avoid this, you can wrap the pasted code in parentheses.

  • Avoid using the pipe operator just once. [link]
  # not preferred
  some_string |> String.downcase()

System.version() |> Version.parse()

preferred

String.downcase(some_string)

Version.parse(System.version())

  • Use bare variables in the first part of a function chain. [link]
  # not preferred
  String.trim(some_string) |> String.downcase() |> String.codepoints()

preferred

some_string |> String.trim() |> String.downcase() |> String.codepoints()

  • Use parentheses when a
    def
    has arguments, and omit them when it doesn't. [link]
  # 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. [link]
  # preferred
  if some_condition, do: # some_stuff
  • Never use
    unless
    with
    else
    . Rewrite these with the positive case first. [link]
  # not preferred
  unless success do
    IO.puts('failure')
  else
    IO.puts('success')
  end

preferred

if success do IO.puts('success') else IO.puts('failure') end

  • Use
    true
    as the last condition of the
    cond
    special form when you need a clause that always matches. [link]
  # not preferred
  cond do
    1 + 2 == 5 ->
      "Nope"

1 + 3 == 5 -&gt;
  "Uh, uh"

:else -&gt;
  "OK"

end

preferred

cond do 1 + 2 == 5 -> "Nope"

1 + 3 == 5 -&gt;
  "Uh, uh"

true -&gt;
  "OK"

end

  • Use parentheses for calls to functions with zero arity, so they can be distinguished from variables. Starting in Elixir 1.4, the compiler will warn you about locations where this ambiguity exists. [link]
  defp do_stuff, do: ...

not preferred

def my_func do # is this a variable or a function call? do_stuff end

preferred

def my_func do # this is clearly a function call do_stuff() end

Naming

  • Use
    snake_case
    for atoms, functions and variables. [link]
  # 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). [link]
  # not preferred
  defmodule Somemodule do
    ...
  end

defmodule Some_Module do ... end

defmodule SomeXml do ... end

preferred

defmodule SomeModule do ... end

defmodule SomeXML do ... end

  • The name of macros suitable for use in guard expressions should be prefixed with
    is_
    . For a list of allowed expressions, see the Guard docs. [link]
  defguard is_cool(var) when var == "cool"
  defguardp is_very_cool(var) when var == "very cool"
  • The names of predicate functions that cannot be used within guards should have a trailing question mark (
    ?
    ) rather than the
    is_
    (or similar) prefix. [link]
  def cool?(var) do
    # Complex check if var is cool not possible in a pure function.
  end
  • Private functions with the same name as public functions should start with
    do_
    . [link]
  def sum(list), do: do_sum(list, 0)

private functions

defp do_sum([], total), do: total defp do_sum([head | tail], total), do: do_sum(tail, head + total)

Comments

  • Write expressive code and try to convey your program's intention through control-flow, structure and naming. [link]

  • Comments longer than a word are capitalized, and sentences use punctuation. Use one space after periods. [link]

  # not preferred
  # these lowercase comments are missing punctuation

preferred

Capitalization example

Use punctuation for complete sentences.

  • Limit comment lines to 100 characters. [link]

Comment Annotations

  • Annotations should usually be written on the line immediately above the relevant code. [link]

  • The annotation keyword is uppercase, and is followed by a colon and a space, then a note describing the problem. [link]

  # 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. [link]
  start_task()

FIXME

Process.sleep(5000)

  • Use

    TODO
    to note missing features or functionality that should be added at a later date. [link]
  • Use

    FIXME
    to note broken code that needs to be fixed. [link]
  • Use

    OPTIMIZE
    to note slow or inefficient code that may cause performance problems. [link]
  • Use

    HACK
    to note code smells where questionable coding practices were used and should be refactored away. [link]
  • 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?
    [link]
  • Use other custom annotation keywords if it feels appropriate, but be sure to document them in your project's

    README
    or similar. [link]

Modules

  • Use one module per file unless the module is only used internally by another module (such as a test). [link]

  • Use

    snake_case
    file names for
    CamelCase
    module names. [link]
  # file is called some_module.ex

defmodule SomeModule do end

  • Represent each level of nesting within a module name as a directory. [link]
  # file is called parser/core/xml_parser.ex

defmodule Parser.Core.XMLParser do end

  • List module attributes, directives, and macros in the following order: [link]
  1. @moduledoc
  2. @behaviour
  3. use
  4. import
  5. alias
  6. require
  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:

  defmodule MyModule do
    @moduledoc """
    An example module
    """

@behaviour MyBehaviour

use GenServer

import Something
import SomethingElse

alias My.Long.Module.Name
alias My.Other.Module.Example

require Integer

@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. [link]
  defmodule SomeProject.SomeModule do
    defstruct [:name]

def name(%__MODULE__{name: name}), do: name

end

  • If you want a prettier name for a module self-reference, set up an alias. [link]
  defmodule SomeProject.SomeModule do
    alias __MODULE__, as: SomeModule

defstruct [:name]

def name(%SomeModule{name: name}), do: name

end

  • Avoid repeating fragments in module names and namespaces. This improves overall readability and eliminates ambiguous aliases. [link]
  # not preferred
  defmodule Todo.Todo do
    ...
  end

preferred

defmodule Todo.Item do ... end

Documentation

Documentation in Elixir (when read either in

iex
with
h
or generated with ExDoc) uses the Module Attributes
@moduledoc
and
@doc
.
  • Always include a
    @moduledoc
    attribute in the line right after
    defmodule
    in your module. [link]
  # not preferred

defmodule AnotherModule do use SomeModule

@moduledoc """
About the module
"""
...

end

preferred

defmodule AThirdModule do @moduledoc """ About the module """

use SomeModule
...

end

  • Use
    @moduledoc false
    if you do not intend on documenting the module. [link]
  defmodule SomeModule do
    @moduledoc false
    ...
  end
  • Separate code after the
    @moduledoc
    with a blank line. [link]
  # not preferred
  defmodule SomeModule do
    @moduledoc """
    About the module
    """
    use AnotherModule
  end

preferred

defmodule SomeModule do @moduledoc """ About the module """

use AnotherModule

end

  • Use heredocs with markdown for documentation. [link]
  # not preferred
  defmodule SomeModule do
    @moduledoc "About the module"
  end

defmodule SomeModule do @moduledoc """ About the module

Examples:
iex&gt; SomeModule.some_function
:result
"""

end

preferred

defmodule SomeModule do @moduledoc """ About the module

## Examples

    iex&gt; SomeModule.some_function
    :result
"""

end

Typespecs

Typespecs are notation for declaring types and specifications, for documentation or for the static analysis tool Dialyzer.

Custom types should be defined at the top of the module with the other directives (see Modules).

  • Place
    @typedoc
    and
    @type
    definitions together, and separate each pair with a blank line. [link]
  defmodule SomeModule do
    @moduledoc false

@typedoc "The name"
@type name :: atom

@typedoc "The result"
@type result :: {:ok, term} | {:error, term}

...

end

  • If a union type is too long to fit on a single line, put each part of the type on a separate line, indented one level past the name of the type. [link]
  # not preferred
  @type long_union_type ::
          some_type | another_type | some_other_type | one_more_type | a_final_type

preferred

@type long_union_type :: some_type | another_type | some_other_type | one_more_type | a_final_type

  • Name the main type for a module
    t
    , for example: the type specification for a struct. [link]
  defstruct [:name, params: []]

@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. [link]
  @doc """
  Some function description.
  """
  @spec some_function(term) :: 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. [link]
  # 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. [link]
  # 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. [link]
  defstruct foo: "test",
            bar: true,
            baz: false,
            qux: false,
            quux: 1

If a multiline struct requires brackets, format it as a multiline list:

  defstruct [
    :name,
    params: [],
    active: true
  ]

Exceptions

  • Make exception names end with a trailing
    Error
    . [link]
  # 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. [link]
  # not preferred
  raise ArgumentError, "This is not valid."

preferred

raise ArgumentError, "this is not valid"

Collections

  • Always use the special syntax for keyword lists. [link]
  # 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. [link]
  # 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. [link]
  # not preferred
  %{"c" => 0, a: 1, b: 2}

preferred

%{:a => 1, :b => 2, "c" => 0}

Strings

  • Match strings using the string concatenator rather than binary patterns: [link]
  # not preferred
  <> = "my string"

preferred

"my" <> _rest = "my string"

Regular Expressions

No guidelines for regular expressions have been added yet.

Metaprogramming

  • Avoid needless metaprogramming. [link]

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. [link]
  # preferred
  assert actual_function(1) == true

not preferred

assert true == actual_function(1)

required - the assertion is a pattern match

assert {:ok, expected} = actual_function(3)

Resources

Alternative Style Guides

Tools

Refer to Awesome Elixir for libraries and tools that can help with code analysis and style linting.

Getting Involved

Contributing

It's our hope that this will become a central hub for community discussion on best practices in Elixir. Feel free to open tickets or send pull requests with improvements. Thanks in advance for your help!

Check the contributing guidelines for more information.

Spread the Word

A community style guide is meaningless without the community's support. Please tweet, star, and let any Elixir programmer know about this guide so they can contribute.

Copying

License

Creative Commons License This work is licensed under a Creative Commons Attribution 3.0 Unported License

Attribution

The structure of this guide, bits of example code, and many of the initial points made in this document were borrowed from the Ruby community style guide. A lot of things were applicable to Elixir and allowed us to get some document out quicker to start the conversation.

Here's the list of people who have kindly contributed to this project.

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.