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
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]
-
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
withelse
. 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 inif
andunless
clauses if it returnsnil
. [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 acond
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
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 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_case
for 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 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 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]
-
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"
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.