Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/lexmag/elixir-style-guide
An opinionated Elixir style guide
https://github.com/lexmag/elixir-style-guide
elixir style-guide
Last synced: about 2 months ago
JSON representation
An opinionated Elixir style guide
- Host: GitHub
- URL: https://github.com/lexmag/elixir-style-guide
- Owner: lexmag
- Created: 2015-10-08T18:52:55.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2020-09-13T20:50:28.000Z (almost 4 years ago)
- Last Synced: 2024-01-27T08:40:46.111Z (5 months ago)
- Topics: elixir, style-guide
- Homepage:
- Size: 47.9 KB
- Stars: 515
- Watchers: 22
- Forks: 35
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
Lists
- awesome-elixir - lexmag/elixir-style-guide - An opinionated Elixir style guide. (Styleguides)
- freaking_awesome_elixir - lexmag/elixir-style-guide - An opinionated Elixir style guide. (Styleguides)
- awesome-elixir - lexmag/elixir-style-guide - An opinionated Elixir style guide. (Styleguides)
- fucking-awesome-elixir - lexmag/elixir-style-guide - An opinionated Elixir style guide. (Styleguides)
README
# 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
* [Linting](#linting)
* [Naming](#naming)
* [Comments](#comments)
* [Modules](#modules)
* [Regular Expressions](#regular-expressions)
* [Structs](#structs)
* [Exceptions](#exceptions)
* [ExUnit](#exunit)The following section are automatically applied by the code formatter in Elixir v1.6 and listed here only for documentation purposes:
* [Formatting](#formatting)
* [Whitespace](#whitespace)
* [Indentation](#indentation)
* [Term representation](#term-representation)
* [Parentheses](#parentheses)
* [Layout](#layout)## Linting
*
Favor the pipeline operator `|>` to chain function calls together.
[[link](#pipeline-operator)]```elixir
# Bad
String.downcase(String.strip(input))# Good
input |> String.strip() |> String.downcase()
```For a multi-line pipeline, place each function call on a new line, and retain the level of indentation.
```elixir
input
|> String.strip()
|> String.downcase()
|> String.slice(1, 3)*
Avoid needless pipelines like the plague.
[[link](#needless-pipeline)]```elixir
# Bad
result = input |> String.strip()# Good
result = String.strip(input)
```*
Don't use anonymous functions in pipelines.
[[link](#anonymous-pipeline)]```elixir
# Bad
sentence
|> String.split(~r/\s/)
|> (fn words -> [@sentence_start | words] end).()
|> Enum.join(" ")# Good
split_sentence = String.split(sentence, ~r/\s/)
Enum.join([@sentence_start | split_sentence], " ")
```Consider defining private helper function when appropriate:
```elixir
# Good
sentence
|> String.split(~r/\s/)
|> prepend(@sentence_start)
|> Enum.join(" ")
```*
Never use `unless` with `else`. Rewrite these with the positive case first.
[[link](#no-else-with-unless)]```elixir
# Bad
unless Enum.empty?(coll) do
:ok
else
:error
end# Good
if Enum.empty?(coll) do
:error
else
:ok
end
```*
Omit the `else` option in `if` and `unless` constructs if `else` returns `nil`.
[[link](#no-nil-else)]```elixir
# 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 `cond` special form, use `true` as its condition.
[[link](#true-in-cond)]```elixir
# Bad
cond do
char in ?0..?9 ->
char - ?0char in ?A..?Z ->
char - ?A + 10:other ->
char - ?a + 10
end# Good
cond do
char in ?0..?9 ->
char - ?0char in ?A..?Z ->
char - ?A + 10true ->
char - ?a + 10
end
```*
Never use `||`, `&&`, and `!` for strictly boolean checks. Use these operators only if any of the arguments are non-boolean.
[[link](#boolean-operators)]```elixir
# 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](#patterns-matching-binaries)]```elixir
# Bad
<<"http://", _rest::bytes>> = input
<> = input# Good
"http://" <> _rest = input
<> <> rest = input
```### Naming
*
Use `snake_case` for functions, variables, module attributes, and atoms.
[[link](#snake-case-atoms-funs-vars-attrs)]```elixir
# Bad
:"no match"
:Error
:badReturnfileName = "sample.txt"
@_VERSION "0.0.1"
def readFile(path) do
# ...
end# Good
:no_match
:error
:bad_returnfile_name = "sample.txt"
@version "0.0.1"
def read_file(path) do
# ...
end
```*
Use `CamelCase` for module names. Keep uppercase acronyms as uppercase.
[[link](#camelcase-modules)]```elixir
# Bad
defmodule :appStack do
# ...
enddefmodule App_Stack do
# ...
enddefmodule Appstack do
# ...
enddefmodule Html do
# ...
end# Good
defmodule AppStack do
# ...
enddefmodule HTML do
# ...
end
```*
The names of predicate functions (functions that return a boolean value) should have a trailing question mark `?` rather than a leading `has_` or similar.
[[link](#predicate-funs-name)]```elixir
# Bad
def is_leap(year) do
# ...
end# Good
def leap?(year) do
# ...
end
```Always use a leading `is_` when naming guard-safe predicate macros.
```elixir
defmacro is_date(month, day) do
# ...
end
```*
Use `snake_case` for naming directories and files, for example `lib/my_app/task_server.ex`.
[[link](#snake-case-dirs-files)]*
Avoid using one-letter variable names.
[[link](#one-letter-var)]### Comments
> Remember, good code is like a good joke: It needs no explanation.
>
> — Russ Olsen*
Use code comments only to communicate important details to another person reading the code.
For example, a high-level description of the algorithm being implemented or why certain
critical decisions, such as optimization or business rules, were made.
[[link](#critical-comments)]*
Avoid superfluous comments.
[[link](#no-superfluous-comments)]```elixir
# Bad
String.first(input) # Get first grapheme.
```### Modules
*
Use a consistent structure when calling `use`/`import`/`alias`/`require`: call them in this order and group multiple calls to each of them.
[[link](#module-layout)]```elixir
use GenServerimport Bitwise
import Kernel, except: [length: 1]alias Mix.Utils
alias MapSet, as: Setrequire Logger
```*
Use the `__MODULE__` pseudo-variable to reference the current module.
[[link](#current-module-reference)]```elixir
# 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 the `String` module are things to start with.
[[link](#pattern-matching-over-regexp)]```elixir
# 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](#non-capturing-regexp)]```elixir
~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](#caret-and-dollar-regexp)]### Structs
*
When calling `defstruct/1`, don't explicitly specify `nil` for fields that default to `nil`.
[[link](#defstruct-fields-default)]```elixir
# Bad
defstruct first_name: nil, last_name: nil, admin?: false# Good
defstruct [:first_name, :last_name, admin?: false]
```### Exceptions
*
Make exception names end with a trailing `Error`.
[[link](#exception-naming)]```elixir
# Bad
BadResponse
ResponseException# Good
ResponseError
```*
Use non-capitalized error messages when raising exceptions, with no trailing punctuation.
[[link](#exception-message)]```elixir
# Bad
raise ArgumentError, "Malformed payload."# Good
raise ArgumentError, "malformed payload"
```There is one exception to the rule - always capitalize Mix error messages.
```elixir
Mix.raise("Could not find dependency")
```### ExUnit
*
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](#exunit-assertion-side)]```elixir
# 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).
```elixir
assert {:error, _reason} = File.stat("./non_existent_file")
```## Formatting
The rules below are automatically applied by the code formatter in Elixir v1.6.
They are provided here for documentation purposes and for those maintaining older codebases.### Whitespace
> Whitespace might be (mostly) irrelevant to the Elixir compiler, but its proper use is the key to writing easily readable code.
*
Avoid trailing whitespaces.
[[link](#no-trailing-whitespaces)]*
End each file with a newline.
[[link](#newline-eof)]*
Use two __spaces__ per indentation level. No hard tabs.
[[link](#spaces-indentation)]```elixir
# Bad
def register_attribute(name, opts) do
register_attribute(__MODULE__, name, opts)
end# Good
def register_attribute(name, opts) do
register_attribute(__MODULE__, name, opts)
end
```*
Use a space before and after binary operators. Use a space after commas `,`, colons `:`, and semicolons `;`. Do not put spaces around matched pairs like brackets `[]`, braces `{}`, and so on.
[[link](#spaces-in-code)]```elixir
# Bad
sum = 1+1
[first|rest] = 'three'
{a1,a2} = {2 ,3}
Enum.join( [ "one" , << "two" >>, sum ])# Good
sum = 1 + 2
[first | rest] = 'three'
{a1, a2} = {2, 3}
Enum.join(["one", <<"two">>, sum])
```*
Use no spaces after unary operators and inside range literals. The only exception is the `not` operator: use a space after it.
[[link](#no-spaces-in-code)]```elixir
# Bad
angle = - 45
^ result = Float.parse("42.01")# Good
angle = -45
^result = Float.parse("42.01")
2 in 1..5
not File.exists?(path)
```*
Use spaces around default arguments `\\` definition.
[[link](#default-arguments)]```elixir
# Bad
def start_link(fun, options\\[])# Good
def start_link(fun, options \\ [])
```*
Do not put spaces around segment options definition in bitstrings.
[[link](#bitstring-segment-options)]```elixir
# Bad
<<102 :: unsigned-big-integer, rest :: binary>>
<<102::unsigned - big - integer, rest::binary>># Good
<<102::unsigned-big-integer, rest::binary>>
```*
Use one space between the leading `#` character of the comment and the text of the comment.
[[link](#leading-space-comment)]```elixir
# Bad
#Amount to take is greater than the number of elements# Good
# Amount to take is greater than the number of elements
```*
Always use a space before `->` in 0-arity anonymous functions.
[[link](#space-before-anonymous-fun-arrow)]```elixir
# Bad
Task.async(fn->
ExUnit.Diff.script(left, right)
end)# Good
Task.async(fn ->
ExUnit.Diff.script(left, right)
end)
```### Indentation
*
Indent the right-hand side of a binary operator one level more than the left-hand side if left-hand side and right-hand side are on different lines. The only exceptions are `when` in guards and `|>`, which go on the beginning of the line and should be indented at the same level as their left-hand side. Do this also for binary operators when assigning.
[[link](#binary-ops-indentation)]```elixir
# Bad"No matching message.\n" <>
"Process mailbox:\n" <>
mailboxmessage =
"No matching message.\n" <>
"Process mailbox:\n" <>
mailboxinput
|> String.strip()
|> String.downcase()defp valid_identifier_char?(char)
when char in ?a..?z
when char in ?A..?Z
when char in ?0..?9
when char == ?_ do
true
enddefp parenless_capture?({op, _meta, _args})
when is_atom(op) and
atom not in @unary_ops and
atom not in @binary_ops do
true
end# Good
"No matching message.\n" <>
"Process mailbox:\n" <>
mailboxmessage =
"No matching message.\n" <>
"Process mailbox:\n" <>
mailboxinput
|> String.strip()
|> String.downcase()defp valid_identifier_char?(char)
when char in ?a..?z
when char in ?A..?Z
when char in ?0..?9
when char == ?_ do
true
enddefp parenless_capture?({op, _meta, _args})
when is_atom(op) and
atom not in @unary_ops and
atom not in @binary_ops do
true
end
```*
Use the indentation shown below for the `with` special form:
[[link](#with-indentation)]```elixir
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 `else` option. If there isn't, the following indentation works as well:
```elixir
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 `for` special form:
[[link](#for-indentation)]```elixir
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 `do` block is short, the following indentation works as well:
```elixir
for partition <- 0..(partitions - 1),
pair <- safe_lookup(registry, partition, key),
into: [],
do: pair
```*
Avoid aligning expression groups:
[[link](#expression-group-alignment)]```elixir
# 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.
*
Use a single level of indentation for multi-line pipelines.
[[link](#pipeline-indentation)]```elixir
input
|> String.strip()
|> String.downcase()
|> String.slice(1, 3)
```### Term representation
*
Add underscores to decimal literals that have six or more digits.
[[link](#underscores-in-numerics)]```elixir
# Bad
num = 1000000
num = 1_500# Good
num = 1_000_000
num = 1500
```*
Use uppercase letters when using hex literals.
[[link](#hex-literals)]```elixir
# Bad
<<0xef, 0xbb, 0xbf>># Good
<<0xEF, 0xBB, 0xBF>>
```*
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](#quotes-around-atoms)]```elixir
# Bad
:'foo-bar'
:'atom number #{index}'# Good
:"foo-bar"
:"atom number #{index}"
```*
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 *not* use a trailing comma on the last element:
[[link](#trailing-comma)]```elixir
[
:foo,
:bar,
:baz
]
```### Parentheses
*
Parentheses are a must for __local__ or __imported__ zero-arity function calls.
[[link](#zero-arity-parens)]```elixir
# Bad
pid = self
import System, only: [schedulers_online: 0]
schedulers_online# Good
pid = self()
import System, only: [schedulers_online: 0]
schedulers_online()
```The same should be done for __remote__ zero-arity function calls:
```elixir
# Bad
Mix.env# Good
Mix.env()
```This rule also applies to one-arity function calls (both local and remote) in pipelines:
```elixir
# Bad
input
|> String.strip
|> decode# Good
input
|> String.strip()
|> decode()
```*
Never wrap the arguments of anonymous functions in parentheses.
[[link](#anonymous-fun-parens)]```elixir
# 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)
```*
Always use parentheses around arguments to definitions (such as `def`, `defp`, `defmacro`, `defmacrop`, `defdelegate`). Don't omit them even when a function has no arguments.
[[link](#fun-parens)]```elixir
# Bad
def main arg1, arg2 do
# ...
enddefmacro env do
# ...
end# Good
def main(arg1, arg2) do
# ...
enddefmacro env() do
# ...
end
```*
Always use parens on zero-arity types.
[[link](#parens-in-zero-arity-types)]```elixir
# Bad
@spec start_link(module, term, Keyword.t) :: on_start# Good
@spec start_link(module(), term(), Keyword.t()) :: on_start()
```### Layout
*
Use one expression per line. Don't use semicolons (`;`) to separate statements and expressions.
[[link](#no-semicolon)]```elixir
# Bad
stacktrace = System.stacktrace(); fun.(stacktrace)# Good
stacktrace = System.stacktrace()
fun.(stacktrace)
```*
When assigning the result of a multi-line expression, begin the expression on a new line.
[[link](#multi-line-expr-assignment)]```elixir
# Bad
{found, not_found} = files
|> Enum.map(&Path.expand(&1, path))
|> Enum.partition(&File.exists?/1)prefix = case base do
:binary -> "0b"
:octal -> "0o"
:hex -> "0x"
end# Good
{found, not_found} =
files
|> Enum.map(&Path.expand(&1, path))
|> Enum.partition(&File.exists?/1)prefix =
case base do
:binary -> "0b"
:octal -> "0o"
:hex -> "0x"
end
```*
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](#binary-operators-at-eols)]```elixir
# Bad"No matching message.\n"
<> "Process mailbox:\n"
<> mailboxinput |>
String.strip() |>
decode()# Good
"No matching message.\n" <>
"Process mailbox:\n" <>
mailboxinput
|> String.strip()
|> decode()
```## License
This work was created by Aleksei Magusev and is licensed under [the CC BY 4.0 license](https://creativecommons.org/licenses/by/4.0).
![Creative Commons License](http://i.creativecommons.org/l/by/4.0/88x31.png)
## Credits
The structure of the guide and some points that are applicable to Elixir were taken from [the community-driven Ruby coding style guide](https://github.com/bbatsov/ruby-style-guide).