Skip to content

Latest commit

 

History

History
1346 lines (944 loc) · 28.8 KB

README-zhCN.md

File metadata and controls

1346 lines (944 loc) · 28.8 KB

目录

序幕

液体架构。像爵士乐一样 - 你们一起即兴演奏,互相回应着对方,你们在创作着音乐,他们在创作着音乐。

—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]

    1. @moduledoc
    2. @behaviour
    3. use
    4. import
    5. alias
    6. require
    7. defstruct
    8. @type
    9. @module_attribute
    10. @callback
    11. @macrocallback
    12. @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
  • 当模块引用自身时使用 __MODULE__ 伪变量。

    如果模块名称修改,可以避免更新对模块自身的引用。

    [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
  • 在文档内使用 heredocsmarkdown

    [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)

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"

正则表达式

暂无内容。

元编程 (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)

更多风格指南

工具

参考 Awesome Elixir 来了解可以帮助代码分析的库和工具。

参与文档

贡献

我们希望这将成为社区讨论 Elixir 最佳实践的中心。

欢迎发起讨论或提交一个带有改进性质的更新请求。在此提前感谢你的帮助!

参考 contributing guidelinescode of conduct 获得更多信息。

口耳相传

一份社区驱动的风格指南,如果没多少人知道, 对一个社区来说就没有多少用处。

请转发,关注这份指南,让每一个 Elixir 程序员都知晓这份指南,让每一个 Elixir 程序员都可以贡献这份指南!

授权

协议

Creative Commons License 本指南基于 Creative Commons Attribution 3.0 Unported License 授权许可。