液体架构。像爵士乐一样 - 你们一起即兴演奏,互相回应着对方,你们在创作着音乐,他们在创作着音乐。
—Frank Gehry
风格很重要。 Elixir 有着大量的风格指南,但是像其他语言一样,这些指南都有可能被扼杀。 请不要扼杀这份指南。
这是一份社群维护的 Elixir 编程语言 风格指南。
欢迎提交 pull requests 和建议来完善这份指南,成为 Elixir 富有活力的社区的一员。
你可以在 Hex 官网 寻找其他的项目来贡献代码。
-
使用两个 空格 进行缩进,不要使用 Hard Tab。 [link]
# 不好 - 四个空格 def some_function do do_something end # 好 def some_function do do_something end
-
使用 Unix 风格换行符 (包括 *BSD/Solaris/Linux/OSX 的用户, Windows 用户要特别小心)。 [link]
-
如果你使用 Git,可以使用下面的配置来避免 Windows 风格换行符: [link]
git config --global core.autocrlf true
-
在运算符的两侧添加空格,在逗号
,
,冒号:
,分号;
,之后添加空格。 不要在配对的括号两侧添加空格,例如,小括号()
,大括号{}
,等等。 空格一般来说对 (大部分) Elixir 编译器是无关紧要的,但是恰当的使用空格是写出可读性高的代码的关键。 [link]sum = 1 + 2 {a, b} = {2, 3} [first | rest] = [1, 2, 3] Enum.map(["one", <<"two">>, "three"], fn num -> IO.puts num end)
-
在仅有一个参数的运算符之后,或者是范围运算符前后,不要添加空格。 [link]
0 - 1 == -1 ^pinned = some_func() 5 in 1..10
-
在
def
直接使用空行,并且把函数分成合乎逻辑的段落。 [link]def some_function(some_data) do altered_data = Module.function(data) 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
-
...但是具有相同函数名的单行
def
写在一起。 [link]def some_function(nil), do: {:err, "No Value"} def some_function([]), do: :ok def some_function([first | rest]) do some_function(rest) end
-
当你使用
do:
的语法声明函数时,如果函数体太长,将do:
放在新的一行,并且进行缩进。 [link]def some_function(args), do: Enum.map(args, fn(arg) -> arg <> " is on a very long line!" end)
当你使用了上面的风格,并且同时
do:
声明多个函数子句时,请把所有的do:
函数子句主体放在# 不好 def some_function([]), do: :empty def some_function(_), do: :very_long_line_here # 好 def some_function([]), do: :empty def some_function(_), do: :very_long_line_here
-
如果你使用了多行的
def
,请不要再使用单行的def
。 [link]def some_function(nil) do {:err, "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
-
使用管道运算符 (
|>
) 时,函数添加括号。 [link]# 不好 some_string |> String.downcase |> String.strip # 好 some_string |> String.downcase() |> String.strip()
-
使用管道运算符链接多个函数。 [link]
# 不好 String.strip(String.downcase(some_string)) # 好 some_string |> String.downcase() |> String.strip() # 多行管道不需要缩进 some_string |> String.downcase() |> String.strip() # 多行管道在模式匹配的右侧要在下一行缩进 sanitized_string = some_string |> String.downcase() |> String.strip()
虽然这是推荐的写法,务必记得在 IEx 中直接粘贴多行管道时,可能会引起错误。这是由于 IEx 会 直接解释第一行,而不会意识到下一行管道的存在。
-
避免只使用一次的管道。 [link]
# 不好 some_string |> String.downcase() # 好 String.downcase(some_string)
-
把纯变量放在函数调用链的开头。
[link]
# 非常不好! # 这会被编译为 String.strip("nope" |> String.downcase()). String.strip "nope" |> String.downcase() # 不好 String.strip(some_string) |> String.downcase() |> String.codepoints() # 好 some_string |> String.strip() |> String.downcase() |> String.codepoints()
-
多行列表进行赋值时,另起一行,并且列表的元素要进行对齐。
[link]
# 不好 - 没有缩进 list = [:first_item, :second_item, :next_item, :last_item] # 好一点 - 进行缩进 list = [:first_item, :second_item, :next_item, :last_item] # 好 - 列表另起一行 # 适合更短,更紧凑的列表 list = [:first_item, :second_item, :next_item, :last_item] # 同样很好 - 列表的每个元素另起一行 # 适合长列表,长元素列表,或者有注释的列表 list = [ :first_item, :second_item, :next_item, # comment :many_items, :last_item ]
-
避免行尾的空白 (trailing whitespace)。 [link]
-
用新的一行来结束源文件。
[link]
-
有参函数使用括号,否则省略括号。
[link]
# 不好 def some_function arg1, arg2 do # body omitted end def some_function() do # body omitted end # 好 def some_function(arg1, arg2) do # body omitted end def some_function do # body omitted end
-
多行赋值后添加空行,表示赋值结束。
[link]
# 不好 some_string = "Hello" |> String.downcase() |> String.strip() another_string <> some_string # 好 some_string = "Hello" |> String.downcase() |> String.strip() another_string <> some_string
# 不好 something = if x == 2 do "Hi" else "Bye" end String.downcase(something) # 好 something = if x == 2 do "Hi" else "Bye" end String.downcase(something)
-
多行
if/unless
时,避免使用do:
。[link]
# 不好 if some_condition, do: # a line of code # another line of code # note no end in this block # 好 if some_condition do # some # lines # of code end
-
单行
if/unless
时使用do:
。[link]
# 好 if some_condition, do: # some_stuff
-
避免使用
unless
搭配else
,将它们改写为肯定条件。[link]
# 不好 unless success? do IO.puts 'failure' else IO.puts 'success' end # 好 if success? do IO.puts 'success' else IO.puts 'failure' end
-
cond
的最后一个条件一定是true
。[link]
# 不好 cond do 1 + 2 == 5 -> "Nope" 1 + 3 == 5 -> "Uh, uh" :else -> "OK" end # 好 cond do 1 + 2 == 5 -> "Nope" 1 + 3 == 5 -> "Uh, uh" true -> "OK" end
-
不要在函数名和左括号之间添加空格。
[link]
# 不好 f (3 + 2) + 1 # 好 f(3 + 2) + 1
-
函数调用时使用括号,特别在使用管道时。
[link]
# 不好 f 3 # 好 f(3) # 不好 2 |> rem 3 |> g # 好 2 |> rem(3) |> g
-
当使用
do
块调用宏时,省略括号。[link]
# 不好 quote(do foo end) # 好 quote do foo end
-
当函数调用在管道之外,并且最后一个参数是函数表达式时,可以选择性的省略括号。
[link]
# 好 Enum.reduce(1..10, 0, fn x, acc -> x + acc end) # 同样好 Enum.reduce 1..10, 0, fn x, acc -> x + acc end
-
无参函数调用时添加括号,以便和变量进行区分。
从 Elixir 1.4 开始,编译器会在有歧义的地方发出警告。
[link]
defp do_stuff, do: ... # 不好 def my_func do do_stuff # 这是变量还是函数调用? end # 好用 def my_func do do_stuff() # 这是一个明确的函数调用 end
-
关键字列表总是使用特殊语法。
[link]
# 不好 some_value = [{:a, "baz"}, {:b, "qux"}] # 好 some_value = [a: "baz", b: "qux"]
-
当关键字列表的括号可选时则省略。
[link]
# 不好 some_function(foo, bar, [a: "baz", b: "qux"]) # 好 some_function(foo, bar, a: "baz", b: "qux")
-
缩排
with
的多个条件,do
的参数在新的一行正常缩进。[link]
with {:ok, foo} <- fetch(opts, :foo), {:ok, bar} <- fetch(opts, :bar), do: {:ok, foo, bar}
-
如果
with
表达式do
块超过了一行,或者使用了else
,请使用多行语法。[link]
with {:ok, foo} <- fetch(opts, :foo), {:ok, bar} <- fetch(opts, :bar) do {:ok, foo, bar} else :error -> {:error, :bad_arg} end
-
符号,方法,变量,使用蛇底式 (
snake_case
)。[link]
# 不好 :"some atom" :SomeAtom :someAtom someVar = 5 def someFunction do ... end def SomeFunction do ... end # 好 :some_atom some_var = 5 def some_function do ... end
-
模块名使用驼峰式 (
CamelCase
) (保留像是 HTTP, RFC, XML 这种缩写为大写形式)。[link]
# 不好 defmodule Somemodule do ... end defmodule Some_Module do ... end defmodule SomeXml do ... end # 好 defmodule SomeModule do ... end defmodule SomeXML do ... end
-
可以在
guard clause
中使用的谓词宏 (编译期生成返回布尔值的函数),命名应该使用is_
前缀。允许的表达式列表,请参考 Guard 文档。
[link]
defmacro is_cool(var) do quote do: unquote(var) == "cool" end
-
谓词函数,无法在
guard clause
中使用,命名时应该以?
结尾,而不是is_
作为前缀。[link]
def cool?(var) do # Complex check if var is cool not possible in a pure function. end
-
当私有函数和公共函数具有相同的名称时,使用
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)
-
编写富有表现力的代码,通过控制流,结构和命名来表达程序的意图。
[link]
-
在注释的
#
之后,保留一个空格。[link]
String.first(some_string) #不好 String.first(some_string) # 好
-
一个字以上的注释需要使用正确的英文大小写以及标点符号,并且在句号后添加空格。
[link]
# 不好 # these lowercase comments are missing punctuation # 好 # Capitalization example # Use punctuation for complete sentences.
-
注解通常写在相关代码的上一行。
[link]
-
注解关键字要大写,紧接着是一个冒号和一个空格,然后是问题的描述。
[link]
# TODO: Deprecate in v1.5. def some_function(arg), do: {:ok, arg}
-
在问题显而易见的情况下,任何说明都是多余的,注解要放到代码的最后并且不带任何解释。
这个用法是特例而不是规则。
[link]
start_task() Process.sleep(5000) # FIXME
-
使用
TODO
来标记未来要实现功能或特性。[link]
-
使用
FIXME
来标记要被修复的代码。[link]
-
使用
OPTIMIZE
来标记可能会引起性能问题的缓慢或者低效的代码。[link]
-
使用
HACK
来比较代码中的坏味道,这些有问题的编码实践应当被重构。[link]
-
使用
REVIEW
来标记需要确认是否正常工作的地方。例如:
REVIEW: Are we sure this is how the client does X currently?
[link] -
如果你觉得需要的话,使用其他自定义的关键字,并将它们记录到
README
或者其他类似的文档中。[link]
-
每个源文件内只有一个模块,除非模块只在另一个模块内部使用 (例如测试)。
[link]
-
文件名使用
蛇底式
(snake_case
),模块名使用驼峰式
(CamelCase
)。[link]
# file is called some_module.ex defmodule SomeModule do end
-
嵌套模块命名中的每一层代表一层文件夹。
[link]
# file is called parser/core/xml_parser.ex defmodule Parser.Core.XMLParser do end
-
defmodule
之后不要添加空行。[link]
-
模块代码之后添加空行。
[link]
-
模块属性和指令要按照下面的顺序:
[link]
@moduledoc
@behaviour
use
import
alias
require
defstruct
@type
@module_attribute
@callback
@macrocallback
@optional_callbacks
在每一组属性或者指令后加入空行,并且本组的项目 (例如,模块名) 要按照字母排序。
这里有一个完整的例子:
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 defstruct name: nil, params: [] @type params :: [{binary, binary}] @module_attribute :foo @other_attribute 100 @callback some_function(term) :: :ok | {:error, term} @macrocallback macro_name(term) :: Macro.t @optional_callbacks macro_name: 1 ... end
-
如果模块名称修改,可以避免更新对模块自身的引用。
[link]
defmodule SomeProject.SomeModule do defstruct [:name] def name(%__MODULE__{name: name}), do: name end
-
设置一个别名,可以让模块名更具可读性。
[link]
defmodule SomeProject.SomeModule do alias __MODULE__, as: SomeModule defstruct [:name] def name(%SomeModule{name: name}), do: name end
-
避免在模块名和命名空间中使用重复的名称,这样提高可读性并且消除 ambiguous aliases。
[link]
# 不好 defmodule Todo.Todo do ... end # 好 defmodule Todo.Item do ... end
使用模块变量 (Module Attributes) @moduledoc
和 @doc
声明文档 (在 iex
使用 h
查看或者使用 ExDoc 生成)。
-
在
defmodule
之后下一行总是定义@moduledoc
变量。[link]
# 不好 defmodule SomeModule do @moduledoc """ About the module """ ... end defmodule AnotherModule do use SomeModule @moduledoc """ About the module """ ... end # 好 defmodule SomeModule do @moduledoc """ About the module """ ... end
-
使用
@moduledoc false
,如果你不想为这个模块增加文档。[link]
defmodule SomeModule do @moduledoc false ... end
-
@moduledoc
之后添加一个空行,与其他代码分开。[link]
# 不好 defmodule SomeModule do @moduledoc """ About the module """ use AnotherModule end # 好 defmodule SomeModule do @moduledoc """ About the module """ use AnotherModule end
-
在文档内使用
heredocs
和markdown
。[link]
# 不好 defmodule SomeModule do @moduledoc "About the module" end defmodule SomeModule do @moduledoc """ About the module Examples: iex> SomeModule.some_function :result """ end # 好 defmodule SomeModule do @moduledoc """ About the module ## Examples iex> SomeModule.some_function :result """ end
Typespecs是用于声明类型和规格的符号,主要用于文档或是静态分析工具,例如 Dialyzer。
自定义类型应当与其他指令一起位于模块的顶部。 (详见 模块).
-
同时使用
@typedoc
和@type
,并且每对之间使用空行间隔。[link]
defmodule SomeModule do @moduledoc false @typedoc "The name" @type name :: atom @typedoc "The result" @type result :: {:ok, term} | {:error, term} ... end
-
如果联合类型 (union type) 声明超过一行,增加新的一行并且使用空格缩进,保持类型对齐。
[link]
# 不好 - 没有缩进 @type long_union_type :: some_type | another_type | some_other_type | a_final_type # 好 @type long_union_type :: some_type | another_type | some_other_type | a_final_type # 同样好 - 每一个类型单独一行 @type long_union_type :: some_type | another_type | some_other_type | a_final_type
-
模块的主类型命名为
t
,例子:结构的类型声明。[link]
defstruct name: nil, params: [] @type t :: %__MODULE__{ name: String.t | nil, params: Keyword.t }
-
将函数的类型声明放到函数定义之上,不用空行间隔。
[link]
@spec some_function(term) :: result def some_function(some_data) do {:ok, some_data} end
-
默认值为
nil
的结构字段使用原子符号列表 (list of atoms),后面紧跟着其他关键字。[link]
# 不好 defstruct name: nil, params: nil, active: true # 好 defstruct [:name, :params, active: true]
-
如果
defstruct
的参数是关键字列表 (keyword list),则省略括号。[link]
# 不好 defstruct [params: [], active: true] # 好 defstruct params: [], active: true # 必须 - 由于至少有一个原子符号,括号不可以省略 defstruct [:name, params: [], active: true]
-
如果结构定义是多行,保持每行第一个键缩进对齐。
[link]
defstruct foo: "test", bar: true, baz: false, qux: false, quux: 1
-
异常的命名以
Error
结尾。[link]
# 不好 defmodule BadHTTPCode do defexception [:message] end defmodule BadHTTPCodeException do defexception [:message] end # 好 defmodule BadHTTPCodeError do defexception [:message] end
-
抛出异常时,异常信息使用小写,并且最后不需要添加标点符号。
[link]
# 不好 raise ArgumentError, "This is not valid." # 好 raise ArgumentError, "this is not valid"
暂无内容。
-
字符串进行模式匹配时,使用字符串拼接的方式而不要使用二进制的方式。
[link]
# 不好 <<"my"::utf8, _rest>> = "my string" # 好 "my" <> _rest = "my string"
暂无内容。
- 避免不必要的元编程。 [link]
-
在编写 ExUnit 断言 (assertions) 时,保持预期值和测试值顺序的一致性。
尽量把预期值放在右边,除非这条断言在进行模式匹配。
[link]
# 好 - 预期值在右边 assert actual_function(1) == true assert actual_function(2) == false # 不好 - 顺序不一致 assert actual_function(1) == true assert false == actual_function(2) # 必要 - 断言是模式匹配 assert {:ok, expected} = actual_function(3)
-
Aleksei Magusev's Elixir Style Guide — An opinionated Elixir style guide stemming from the coding style practiced in the Elixir core libraries. Developed by Aleksei Magusev and Andrea 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 by Credo static code analysis tool.
参考 Awesome Elixir 来了解可以帮助代码分析的库和工具。
我们希望这将成为社区讨论 Elixir 最佳实践的中心。
欢迎发起讨论或提交一个带有改进性质的更新请求。在此提前感谢你的帮助!
参考 contributing guidelines 和 code of conduct 获得更多信息。
一份社区驱动的风格指南,如果没多少人知道, 对一个社区来说就没有多少用处。
请转发,关注这份指南,让每一个 Elixir 程序员都知晓这份指南,让每一个 Elixir 程序员都可以贡献这份指南!
本指南基于 Creative Commons Attribution 3.0 Unported License 授权许可。