Liquid architecture. It's like jazz — you improvise, you work together, you play off each other, you make something, they make something.
—Frank Gehry
程式的語法風格很重要! Elixir has plenty of style but like all languages it can be stifled. Don't stifle the style.
這個社群的風格指南嘗試提供一個社群維護的 Elixir 程式語言 的語法風格, 歡迎提出 Pull Request 來協助完善這份指南。
我們希望 Elixir 這個語言能夠像那些在它之前的語言一樣有個活躍的社群!
如果你想要找其他的 Project 來提出貢獻,請上 Hex package manager site。
-
請用兩個空格來縮排, 不要用 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
-
請使用管線運算子(
|>
; pipe operator)鏈接多個函式。 [link]# 不好 String.strip(String.downcase(some_string)) # 好 some_string |> String.downcase |> String.strip # 多行管線不用縮排 some_string |> String.downcase |> String.strip # 如果多行管線用在模式比對(pattern match)的右側,請換行並縮排 sanitized_string = some_string |> String.downcase |> String.strip
雖然這是推薦的寫法,務必記得在 IEx 中直接貼上多行管線時,很有可能會出錯。 這是因為 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
-
避免行尾的空白(trailing whitespace)。 [link]
-
用新的一行來結尾每一個檔案。 [link]
-
Use parentheses when a
def
has arguments, and omit them when it doesn't. 如果def
有參數請用括號括起,如果沒有參數請不要使用括號。 [link]# 不好 def some_function arg1, arg2 do # 省略 end def some_function() do # 省略 end # 好 def some_function(arg1, arg2) do # 省略 end def some_function do # 省略 end
-
Never use
do:
for multi-lineif/unless
. 多行if/unless
時,不要使用do:
[link]# 不好 if some_condition, do: # 一行程式碼 # 又一行程式碼 # note 這個程式主體沒有結束 # 好 if some_condition do # 幾 # 行 # 程式碼 end
-
Use
do:
for single lineif/unless
statements. 單行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" true -> "OK" end
-
不要在函式名與左括號後之間使用空白。 [link]
# 不好 f (3 + 2) + 1 # 好 f(3 + 2) + 1
-
在使用函式時使用括號,特別是用在管線鍊時。 [link]
# 不好 f 3 # 好 f(3) # 不好,此方法解讀為 rem(2, (3 |> g)),這應該不是你想要的。 2 |> rem 3 |> g # 好 2 |> rem(3) |> g
-
在使用
quote
編輯巨集時,不要使用括號在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
-
當呼叫無參數的函式時,加上括號以便與變數區分。 [link]
defp do_stuff, do: ... # 不好 def my_func do do_stuff # 這是變數還是函式呼叫? end # 好 def my_func do do_stuff() # 這很明確是一個函式 end
-
利用縮排來排列每個
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]
# not preferred 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) # 私有函數 defp do_sum([], total), do: total defp do_sum([head | tail], total), do: do_sum(tail, head + total)
-
盡可能利用控制流、結構、和命名來表達你的程式的意圖。 [link]
-
在註解的
#
與註解文字保留一空格。 [link] -
一個字以上的註釋需要使用正確的英文大寫與標點符號規則,並在句號後 加上一空格。 [link]
# 不好 String.upcase(some_string) # Capitalize string.
-
註釋請寫在相關程式碼的上一行。 [link]
-
註釋關鍵字後方伴隨著一個冒號及空白,接著一個描述問題的記錄。 [link]
-
如果需要用多行來描述問題,之後的行要放在
#
號後面並縮排兩個空格。 [link] -
在問題顯而易見並任何說明都是多餘的狀況下,註釋會被放在該程式碼的最後並不帶任何解釋。 這個用法是特例而不是規則。 [link]
-
使用
TODO
來標記之後應被加入的未實現功能或特色。 [link] -
使用
FIXME
來標記一個需要修復的程式碼。 [link] -
使用
OPTIMIZE
來標記可能影響效能的緩慢或效率低落的程式碼。 [link] -
使用
HACK
來標記代碼異味,其中包含了有問題的實作與及應該被重構的程式碼。 [link] -
使用
REVIEW
來標記任何需要審視及確認正常動作的地方。 舉例來說:REVIEW: 我們確定用戶現在是這麼做的嗎?
[link] -
如果你覺得適當的話,使用其他你習慣的註釋關鍵字,但記得把它們記錄在專案的
README
或類似的地方。 [link]
-
Use one module per file unless the module is only used internally by another module (such as a test). 每一個檔案內只有一個模組,除非另一個模組只有被並存的模組使用(如測試)。 [link]
-
使用小寫底線檔名(snake_case)配合駝峰式(CamelCase)模組名。 [link]
# 檔名: some_module.ex defmodule SomeModule do end
-
Represent each level of nesting within a module name as a directory. 用模組名中的階層來表示檔案位置。 [link]
# 檔案名為 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
在每個屬性後加入空行,並依照字母順序整理。 以下為完整範例:
defmodule MyModule do @moduledoc """ An example module """ @behaviour MyBehaviour use GenServer import Something import SomethingElse alias My.Long.Module.Name alias My.Other.Module.Name require Integer defstruct name: nil, params: [] @type params :: [{binary, binary}] @module_attribute :foo @other_attribute 100 ... end
-
當在模組內參考自己,請使用
__MODULE__
虛擬變數。如模組名修改,將不用另外 更新這些自我參考。 [link]defmodule SomeProject.SomeModule do defstruct [:name] def name(%__MODULE__{name: name}), do: name end
-
如果你想要比較美觀的自我參考,請使用
alias
。 [link]defmodule SomeProject.SomeModule do alias __MODULE__, as: SomeModule defstruct [:name] def name(%SomeModule{name: name}), do: name end
Elixir 的文件(當在 iex
的 h
指令或是用 ExDoc 產生)是指 @moduledoc
和 @doc
的[模組變數](Module Attributes)。
-
在
defmodule
模組定義的下一行務必要是@moduledoc
模組變數。 [link]# 不好 defmodule SomeModule do @moduledoc """ 關於模組 """ ... end defmodule AnotherModule do use SomeModule @moduledoc """ 關於模組 """ ... end # 好 defmodule SomeModule do @moduledoc """ 關於模組 """ ... end
-
使用
@moduledoc false
如果你不想為這個模組增加文件。 [link]defmodule SomeModule do @moduledoc false ... end
-
在
@moduledoc
後加一空行,與程式碼分開。 [link]# 不好 defmodule SomeModule do @moduledoc """ 關於模組 """ use AnotherModule end # 好 defmodule SomeModule do @moduledoc """ 關於模組 """ use AnotherModule end
-
在文件內使用 heredocs 和 markdown。 [link]
# 不好 defmodule SomeModule do @moduledoc "About the module" end defmodule SomeModule do @moduledoc """ 關於模組 Examples: iex> SomeModule.some_function :result """ end # 好 defmodule SomeModule do @moduledoc """ 關於模組 ## 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 types) 宣告會超過一行,新的一行應用空格縮排。 [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, params: Keyword.t }
-
把函式型別宣告放在
def
的上一行,不需要空行。 [link]@spec some_function(term) :: result def some_function(some_data) do {:ok, some_data} end
-
如果此結構的所有欄位皆為空值,使用 atoms 的串列表示。 [link]
# 不好 defstruct name: nil, params: nil # 好 defstruct [:name, :params]
-
如果結構宣告佔兩行以上,用空格縮排並對齊第一個 key。 [link]
defstruct foo: "test", bar: true, baz: false, qux: false, quux: nil
-
Make exception names end with a trailing
Error
. 自訂例外的結尾應為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"
No guidelines for collections have been added yet.
-
使用字串連接(string concatenatora)做模式比對,不要用二進制的模式。 [link]
# 不好 <<"my"::utf8, _rest>> = "my string" # 好 "my" <> _rest = "my string"
No guidelines for regular expressions have been added yet.
- 避免不必要的元編程(metaprogramming)。 [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)
推薦的其它寫法是在社群中還不常見,但有其價值的編程風格。
-
Atom 為真值(truthy),可作為
cond
條件式內全捕捉(catch-all)條件。 建議使用:else
或是:otherwise
。 [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
到 Awesome Elixir 上找到更多的風格指南
Refer to Awesome Elixir for libraries and tools that can help with code analysis and style linting.
It's our hope that this will become a central hub for community discussion on best practices in Elixir. Feel free to open tickets or send pull requests with improvements. Thanks in advance for your help!
Check the contributing guidelines and code of conduct for more information.
A community style guide is meaningless without the community's support. Please tweet, star, and let any Elixir programmer know about this guide so they can contribute.
This work is licensed under a
Creative Commons Attribution 3.0 Unported License
The structure of this guide, bits of example code, and many of the initial points made in this document were borrowed from the Ruby community style guide. A lot of things were applicable to Elixir and allowed us to get some document out quicker to start the conversation.
Here's the list of people who has kindly contributed to this project.