- Notifications
You must be signed in to change notification settings - Fork301
A community driven style guide for Elixir
christopheradams/elixir_style_guide
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Liquid architecture. It's like jazz — you improvise, you work together, youplay 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.
This is community style guide for theElixir programming language.Please feel free to make pull requests and suggestions, and be a part ofElixir's vibrant community.
If you're looking for other projects to contribute to please see theHex package manager site.
Translations of the guide are available in the following languages:
Elixir v1.6 introduced aCode Formatter andMix 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, butare provided here as examples of the preferred style.
Avoid trailing whitespace.[link]
End each file with a newline.[link]
Use Unix-style line endings (*BSD/Solaris/Linux/OSX users are covered bydefault, Windows users have to be extra careful).[link]
If you're using Git you might want to add the following configurationsetting to protect your project from Windows line endings creeping in:[link]
git config --global core.autocrlftrue
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 properuse is the key to writing easily readable code.[link]
sum=1+2{a,b}={2,3}[first|rest]=[1,2,3]Enum.map(["one",<<"two">>,"three"],fnnum->IO.puts(num)end)
Do not use spaces after non-word operators that only take one argument; oraround the range operator.[link]
0-1==-1^pinned=some_func()5in1..10
Use blank lines between
def
s to break up a function into logicalparagraphs.[link]defsome_function(some_data)dosome_data|>other_function()|>List.first()enddefsome_functiondoresultenddefsome_other_functiondoanother_resultenddefa_longer_functiondoonetwothreefourend
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, putdo:
on a new line, indented one level more than the previous line.[link]defsome_function([:foo,:bar,:baz]=args),do:Enum.map(args,fnarg->arg<>" is on a very long line!"end)
When the
do:
clause starts on its own line, treat it as a multilinefunction by separating it with blank lines.# not preferreddefsome_function([]),do::emptydefsome_function(_),do::very_long_line_here# preferreddefsome_function([]),do::emptydefsome_function(_),do::very_long_line_here
Add a blank line after a multiline assignment as avisual cue that the assignment is 'over'.[link]
# not preferredsome_string="Hello"|>String.downcase()|>String.trim()another_string<>some_string# preferredsome_string="Hello"|>String.downcase()|>String.trim()another_string<>some_string
# also not preferredsomething=ifx==2do"Hi"else"Bye"endString.downcase(something)# preferredsomething=ifx==2do"Hi"else"Bye"endString.downcase(something)
If a list, map, or struct spans multiple lines, put each element, as well asthe 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 sameline as the assignment.[link]
# not preferredlist=[:first_item,:second_item]# preferredlist=[:first_item,:second_item]
If any
case
orcond
clause needs more than one line (due to line length,multiple expressions in the clause body, etc.), use multi-line syntax for allclauses, and separate each one with a blank line.[link]# not preferredcaseargdotrue->IO.puts("ok");:okfalse->:errorend# not preferredcaseargdotrue->IO.puts("ok"):okfalse->:errorend# preferredcaseargdotrue->IO.puts("ok"):okfalse->:errorend
Place comments above the line they comment on.[link]
String.first(some_string)# not preferred# preferredString.first(some_string)
Use one space between the leading
#
character of the comment and the text ofthe comment.[link]#not preferredString.first(some_string)# preferredString.first(some_string)
Indent and align successive
with
clauses.Put thedo:
argument on a new line, aligned with the previous clauses.[link]with{:ok,foo}<-fetch(opts,:foo),{:ok,my_var}<-fetch(opts,:my_var),do:{:ok,foo,my_var}
If the
with
expression has ado
block with more than one line, or has anelse
option, use multiline syntax.[link]with{:ok,foo}<-fetch(opts,:foo),{:ok,my_var}<-fetch(opts,:my_var)do{:ok,foo,my_var}else:error->{:error,:bad_arg}end
Use parentheses for one-arity functions when using the pipe operator (
|>
).[link]# not preferredsome_string|>String.downcase|>String.trim# preferredsome_string|>String.downcase()|>String.trim()
Never put a space between a function name and the opening parenthesis.[link]
# not preferredf(3+2)# preferredf(3+2)
Use parentheses in function calls, especially inside a pipeline.[link]
# not preferredf3# preferredf(3)# not preferred and parses as rem(2, (3 |> g)), which is not what you want.2|>rem3|>g# preferred2|>rem(3)|>g()
Omit square brackets from keyword lists whenever they are optional.[link]
# not preferredsome_function(foo,bar,[a:"baz",b:"qux"])# preferredsome_function(foo,bar,a:"baz",b:"qux")
The rules in this section may not be applied by the code formatter, but they aregenerally preferred practice.
Run single-line
def
s that match for the same function together, but separatemultilinedef
s with a blank line.[link]defsome_function(nil),do:{:error,"No Value"}defsome_function([]),do::okdefsome_function([first|rest])dosome_function(rest)end
If you have more than one multiline
def
, do not use single-linedef
s.[link]defsome_function(nil)do{:error,"No Value"}enddefsome_function([])do:okenddefsome_function([first|rest])dosome_function(rest)enddefsome_function([first|rest],opts)dosome_function(rest,opts)end
Use the pipe operator to chain functions together.[link]
# not preferredString.trim(String.downcase(some_string))# preferredsome_string|>String.downcase()|>String.trim()# Multiline pipelines are not further indentedsome_string|>String.downcase()|>String.trim()# Multiline pipelines on the right side of a pattern match# should be indented on a new linesanitized_string=some_string|>String.downcase()|>String.trim()
While this is the preferred method, take into account that copy-pastingmultiline pipelines into IEx might result in a syntax error, as IEx willevaluate 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 preferredsome_string|>String.downcase()System.version()|>Version.parse()# preferredString.downcase(some_string)Version.parse(System.version())
Usebare variables in the first part of a function chain.[link]
# not preferredString.trim(some_string)|>String.downcase()|>String.codepoints()# preferredsome_string|>String.trim()|>String.downcase()|>String.codepoints()
Use parentheses when a
def
has arguments, and omit them when it doesn't.[link]# not preferreddefsome_functionarg1,arg2do# body omittedenddefsome_function()do# body omittedend# preferreddefsome_function(arg1,arg2)do# body omittedenddefsome_functiondo# body omittedend
Use
do:
for single lineif/unless
statements.[link]# preferredifsome_condition,do:# some_stuff
Never use
unless
withelse
.Rewrite these with the positive case first.[link]# not preferredunlesssuccessdoIO.puts('failure')elseIO.puts('success')end# preferredifsuccessdoIO.puts('success')elseIO.puts('failure')end
Use
true
as the last condition of thecond
special form when you need aclause that always matches.[link]# not preferredconddo1+2==5->"Nope"1+3==5->"Uh, uh":else->"OK"end# preferredconddo1+2==5->"Nope"1+3==5->"Uh, uh"true->"OK"end
Use parentheses for calls to functions with zero arity, so they can bedistinguished from variables.Starting in Elixir 1.4, the compiler will warn you aboutlocations where this ambiguity exists.[link]
defpdo_stuff,do:...# not preferreddefmy_funcdo# is this a variable or a function call?do_stuffend# preferreddefmy_funcdo# this is clearly a function calldo_stuff()end
This guide follows theNaming Conventions from the Elixir docs,including the use ofsnake_case
andCamelCase
to describe the casingrules.
Use
snake_case
for atoms, functions and variables.[link]# not preferred:"some atom":SomeAtom:someAtomsomeVar=5defsomeFunctiondo...end# preferred:some_atomsome_var=5defsome_functiondo...end
Use
CamelCase
for modules (keep acronyms like HTTP, RFC, XML uppercase).[link]# not preferreddefmoduleSomemoduledo...enddefmoduleSome_Moduledo...enddefmoduleSomeXmldo...end# preferreddefmoduleSomeModuledo...enddefmoduleSomeXMLdo...end
Functions that return a boolean (
true
orfalse
) should be namedwith a trailing question mark.[link]defcool?(var)doString.contains?(var,"cool")end
Boolean checks that can be used in guard clauses should be named withan
is_
prefix.For a list of allowed expressions, see theGuard docs.[link]defguardis_cool(var)whenvar=="cool"defguardis_very_cool(var)whenvar=="very cool"
Private functions should not have the same name as public functions.Also, the
def name
anddefp do_name
pattern is discouraged.Usually one can try to find more descriptive names focusing on the differences.[link]
defsum(list),do:sum_total(list,0)# private functionsdefpsum_total([],total),do:totaldefpsum_total([head|tail],total),do:sum_total(tail,head+total)
Write expressive code and try to convey your program's intention throughcontrol-flow, structure and naming.[link]
Comments longer than a word are capitalized, and sentences use punctuation.Useone 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]
Annotations should usually be written on the line immediately above therelevant 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.defsome_function(arg),do:{:ok,arg}
In cases where the problem is so obvious that any documentation would beredundant, annotations may be left with no note.This usage should be the exception and not the rule.[link]
start_task()# FIXMEProcess.sleep(5000)
Use
TODO
to note missing features or functionality that should be added at alater 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 performanceproblems.[link]Use
HACK
to note code smells where questionable coding practices were usedand should be refactored away.[link]Use
REVIEW
to note anything that should be looked at to confirm it isworking 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 todocument them in your project's
README
or similar.[link]
Use one module per file unless the module is only used internally by anothermodule (such as a test).[link]
Use
snake_case
file names forCamelCase
module names.[link]# file is called some_module.exdefmoduleSomeModuledoend
Represent each level of nesting within a module name as a directory.[link]
# file is called parser/core/xml_parser.exdefmoduleParser.Core.XMLParserdoend
List module attributes, directives, and macros in the following order:[link]
@moduledoc
@behaviour
use
import
require
alias
@module_attribute
defstruct
@type
@callback
@macrocallback
@optional_callbacks
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:
defmoduleMyModuledo@moduledoc""" An example module """@behaviourMyBehaviouruseGenServerimportSomethingimportSomethingElserequireIntegeraliasMy.Long.Module.NamealiasMy.Other.Module.Example@module_attribute:foo@other_attribute100defstruct[:name,params:[]]@typeparams::[{binary,binary}]@callbacksome_function(term):::ok|{:error,term}@macrocallbackmacro_name(term)::Macro.t()@optional_callbacksmacro_name:1@docfalsedefmacro__using__(_opts),do::no_op@doc""" Determines when a term is `:ok`. Allowed in guards. """defguardis_ok(term)whenterm==:ok@impltruedefinit(state),do:{:ok,state}# Define other functions here.end
Use the
__MODULE__
pseudo variable when a module refers to itself. Thisavoids having to update any self-references when the module name changes.[link]defmoduleSomeProject.SomeModuledodefstruct[:name]defname(%__MODULE__{name:name}),do:nameend
If you want a prettier name for a module self-reference, set up an alias.[link]
defmoduleSomeProject.SomeModuledoalias__MODULE__,as:SomeModuledefstruct[:name]defname(%SomeModule{name:name}),do:nameend
Avoid repeating fragments in module names and namespaces.This improves overall readability andeliminatesambiguous aliases.[link]
# not preferreddefmoduleTodo.Tododo...end# preferreddefmoduleTodo.Itemdo...end
Documentation in Elixir (when read either iniex
withh
or generated withExDoc) uses theModule Attributes@moduledoc
and@doc
.
Always include a
@moduledoc
attribute in the line right afterdefmodule
inyour module.[link]# not preferreddefmoduleAnotherModuledouseSomeModule@moduledoc""" About the module """...end# preferreddefmoduleAThirdModuledo@moduledoc""" About the module """useSomeModule...end
Use
@moduledoc false
if you do not intend on documenting the module.[link]defmoduleSomeModuledo@moduledocfalse...end
Separate code after the
@moduledoc
with a blank line.[link]# not preferreddefmoduleSomeModuledo@moduledoc""" About the module """useAnotherModuleend# preferreddefmoduleSomeModuledo@moduledoc""" About the module """useAnotherModuleend
Use heredocs with markdown for documentation.[link]
# not preferreddefmoduleSomeModuledo@moduledoc"About the module"enddefmoduleSomeModuledo@moduledoc""" About the module Examples: iex> SomeModule.some_function :result """end# preferreddefmoduleSomeModuledo@moduledoc""" About the module ## Examples iex> SomeModule.some_function :result """end
Typespecs are notation for declaring types and specifications, fordocumentation or for the static analysis tool Dialyzer.
Custom types should be defined at the top of the module with the otherdirectives (seeModules).
Place
@typedoc
and@type
definitions together, and separate eachpair with a blank line.[link]defmoduleSomeModuledo@moduledocfalse@typedoc"The name"@typename::atom@typedoc"The result"@typeresult::{:ok,term}|{:error,term}...end
If a union type is too long to fit on a single line, put each part of thetype on a separate line, indented one level past the name of the type.[link]
# not preferred@typelong_union_type::some_type|another_type|some_other_type|one_more_type|a_final_type# preferred@typelong_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 astruct.[link]defstruct[:name,params:[]]@typet::%__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."""@specsome_function(term)::resultdefsome_function(some_data)do{:ok,some_data}end
Use a list of atoms for struct fields that default to
nil
, followed by theother keywords.[link]# not preferreddefstructname:nil,params:nil,active:true# preferreddefstruct[:name,:params,active:true]
Omit square brackets when the argument of a
defstruct
is a keyword list.[link]# not preferreddefstruct[params:[],active:true]# preferreddefstructparams:[],active:true# required - brackets are not optional, with at least one atom in the listdefstruct[:name,params:[],active:true]
If a struct definition spans multiple lines, put each element on its own line,keeping the elements aligned.[link]
defstructfoo:"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]
Make exception names end with a trailing
Error
.[link]# not preferreddefmoduleBadHTTPCodedodefexception[:message]enddefmoduleBadHTTPCodeExceptiondodefexception[:message]end# preferreddefmoduleBadHTTPCodeErrordodefexception[:message]end
Use lowercase error messages when raising exceptions, with no trailingpunctuation.[link]
# not preferredraiseArgumentError,"This is not valid."# preferredraiseArgumentError,"this is not valid"
Always use the special syntax for keyword lists.[link]
# not preferredsome_value=[{:a,"baz"},{:b,"qux"}]# preferredsome_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}
Match strings using the string concatenator rather than binary patterns:[link]
# not preferred<<"my"::utf8,_rest::bytes>>="my string"# preferred"my"<>_rest="my string"
No guidelines for regular expressions have been added yet.
- Avoid needless metaprogramming.[link]
When writingExUnit assertions, put the expression being tested to the leftof the operator, and the expected result to the right, unless the assertion isa pattern match.[link]
# preferredassertactual_function(1)==true# not preferredasserttrue==actual_function(1)# required - the assertion is a pattern matchassert{:ok,expected}=actual_function(3)
Aleksei Magusev's Elixir Style Guide— An opinionated Elixir style guide stemming from the coding style practicedin the Elixir core libraries.Developed byAleksei Magusev andAndrea Leopardi, members of Elixir core team.While the Elixir project doesn't adhere to any specific style guide,this is the closest available guide to its conventions.
Credo's Elixir Style Guide— Style Guide for the Elixir language, implemented byCredo static code analysis tool.
Refer toAwesome Elixir for libraries and tools that can helpwith code analysis and style linting.
It's our hope that this will become a central hub for community discussion onbest practices in Elixir.Feel free to open tickets or send pull requests with improvements.Thanks in advance for your help!
Check thecontributing guidelines for more information.
A community style guide is meaningless without the community's support. Pleasetweet,star, and let any Elixir programmer knowaboutthis guide so they can contribute.
This work is licensed under aCreative Commons Attribution 3.0 Unported License
The structure of this guide, bits of example code, and many of the initialpoints made in this document were borrowed from theRuby community style guide.A lot of things were applicable to Elixir and allowed us to getsome documentout quicker to start the conversation.
Here's thelist of people who have kindly contributed to thisproject.
About
A community driven style guide for Elixir
Topics
Resources
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.