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
-
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
notoperator. [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
whenguard clauses on the same level as the function/macro signature in the definition they're part of. Do this only if you cannot fit thewhenguard on the same line as the definition. [link]def format_error({exception, stacktrace}) when is_list(stacktrace) and stacktrace != [] do # ... end defmacro dngettext(domain, msgid, msgid_plural, count) when is_binary(msgid) and is_binary(msgid_plural) 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
-
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}"
-
Avoid trailing whitespaces. [link]
-
End each file with a newline. [link]
-
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 use a trailing comma even for the last element: [link]
[ :foo, :bar, :baz, ]
-
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.
-
Always use parentheses around
defarguments, 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 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)
-
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 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 # Good "No matching message.\n" <> "Process mailbox:\n" <> mailbox
-
Use the indentation shown below for the
withspecial form: [link]with {year, ""} <- Integer.parse(year), {month, ""} <- Integer.parse(month), {day, ""} <- Integer.parse(day) do new(year, month, day) else _ -> {:error, :invalid_format} end
Always use the indentation above if there's an
elseoption. If there isn't, the following indentation works as well:with {:ok, date} <- Calendar.ISO.date(year, month, day), {:ok, time} <- Time.new(hour, minute, second, microsecond), do: new(date, time)
-
Use the indentation shown below for the
forspecial form: [link]for {alias, _module} <- aliases_from_env(server), [name] = Module.split(alias), starts_with?(name, hint), into: [] do %{kind: :module, type: :alias, name: name} end
If the body of the
doblock is short, the following indentation works as well:for partition <- 0..(partitions - 1), pair <- safe_lookup(registry, partition, key), into: [], do: pair
-
Never use
unlesswithelse. 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
elseoption inifandunlessclauses if it returnsnil. [link]# Bad if byte_size(data) > 0, do: data, else: nil # Good if byte_size(data) > 0, do: data
-
If you have an always-matching clause in the
condspecial form, usetrueas its condition. [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>>
-
Use
snake_casefor 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
CamelCasefor 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 leadinghas_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_casefor naming directories and files, e.g.lib/my_app/task_server.ex. [link] -
Avoid using one-letter variable names. [link]
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.
-
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
__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 are the last resort. Pattern matching and
Stringmodule 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:\Aand\z(not to be confused with\Zwhich is the equivalent of\n?\z). [link]
-
When calling
defstruct/1, don't explicitly specifynilfor fields that default tonil. [link]# Bad defstruct first_name: nil, last_name: nil, admin?: false # Good defstruct [:first_name, :last_name, admin?: false]
-
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"
-
Never 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
-
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")
This work was created by Aleksei Magusev and is licensed under the CC BY 4.0 license.
The structure of the guide and some points that are applicable to Elixir were taken from the community-driven Ruby coding style guide.
