Skip to content

Latest commit

 

History

History
1103 lines (849 loc) · 25.7 KB

README_zhTW.md

File metadata and controls

1103 lines (849 loc) · 25.7 KB

Table of Contents

前言

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-line if/unless. 多行 if/unless 時,不要使用 do: [link]

    # 不好
    if some_condition, do:
      # 一行程式碼
      # 又一行程式碼
      # note 這個程式主體沒有結束
    
    # 好
    if some_condition do
      # 幾
      # 行
      # 程式碼
    end
  • Use do: for single line if/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]

    1. @moduledoc
    2. @behaviour
    3. use
    4. import
    5. alias
    6. require
    7. defstruct
    8. @type
    9. @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 的文件(當在 iexh 指令或是用 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 上找到更多的風格指南

Tools

Refer to Awesome Elixir for libraries and tools that can help with code analysis and style linting.

Getting Involved

Contributing

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.

Spread the Word

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.

Copying

License

Creative Commons License This work is licensed under a Creative Commons Attribution 3.0 Unported License

Attribution

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.