查看源代码 命名约定

本文档是 Elixir 命名约定的参考,涵盖从大小写到标点符号的各个方面。

命名约定本质上是 Elixir 语法的一个子集。约定旨在遵循并设定语言和社区的最佳实践。如果你想要了解 Elixir 语法的完整参考,而不仅仅是约定,请参阅 语法参考

大小写

Elixir 开发人员在定义变量、函数名称、模块属性等时必须使用 snake_case

some_map = %{this_is_a_key: "and a value"}
is_map(some_map)

别名通常用作模块名称,它们是例外,必须大写并使用 CamelCase,例如 OptionParser。对于别名,首字母缩略词中的大写字母要保留,例如 ExUnit.CaptureIOMix.SCM

原子可以写成 :snake_case:CamelCase,虽然在整个 Elixir 中使用蛇形大小写是约定俗成的。

一般来说,文件名遵循其定义的模块的 snake_case 约定。例如,MyApp 应该在 my_app.ex 文件中定义。但是,这只是一个约定。最终,任何文件名都可以使用,因为它们不会影响编译后的代码。

下划线 (_foo)

Elixir 在不同的情况下依赖下划线。

例如,不打算使用的值必须赋值给 _ 或以下划线开头的变量。

iex> {:ok, _contents} = File.read("README.md")

函数名称也可以以下划线开头。这样的函数默认情况下永远不会导入。

iex> defmodule Example do
...>   def _wont_be_imported do
...>     :oops
...>   end
...> end

iex> import Example
iex> _wont_be_imported()
** (CompileError) iex:1: undefined function _wont_be_imported/0

由于这个特性,Elixir 依赖以下划线开头的函数来将编译时元数据附加到模块。这些函数通常采用 __foo__ 格式。例如,Elixir 中的每个模块都有一个 __info__/1 函数。

iex> String.__info__(:functions)
[at: 2, capitalize: 1, chunk: 2, ...]

Elixir 还包含五个遵循双下划线格式的特殊形式:__CALLER__/0__DIR__/0__ENV__/0__MODULE__/0 检索当前环境的编译时信息,而 __STACKTRACE__/0 检索当前异常的堆栈跟踪。

尾部感叹号 (foo!)

尾部感叹号(惊叹号)表示函数或宏在失败的情况下会引发异常。

许多函数成对出现,例如 File.read/1File.read!/1File.read/1 将返回一个成功或失败元组,而 File.read!/1 将返回一个普通值,否则会引发异常。

iex> File.read("file.txt")
{:ok, "file contents"}
iex> File.read("no_such_file.txt")
{:error, :enoent}

iex> File.read!("file.txt")
"file contents"
iex> File.read!("no_such_file.txt")
** (File.Error) could not read file no_such_file.txt: no such file or directory

当你想要使用模式匹配来处理不同的结果时,首选没有 ! 的版本。

case File.read(file) do
  {:ok, body} -> # do something with the `body`
  {:error, reason} -> # handle the error caused by `reason`
end

但是,如果你希望结果始终成功(例如,如果你希望文件始终存在),感叹号变体可能更方便,并且在失败时会引发更具帮助的错误消息(而不是模式匹配失败)。

在考虑函数的失败情况时,我们严格考虑其域内发生的错误,例如无法打开文件。来自无效参数类型的错误必须始终引发,无论函数是否有感叹号。异常通常是 ArgumentError 或详细的 FunctionClauseError

iex(1)> File.read(123)
** (FunctionClauseError) no function clause matching in IO.chardata_to_string/1

    The following arguments were given to IO.chardata_to_string/1:

        # 1
        123

    Attempted function clauses (showing 2 out of 2):

        def chardata_to_string(string) when is_binary(string)
        def chardata_to_string(list) when is_list(list)

成对函数的更多示例:Base.decode16/2Base.decode16!/2File.cwd/0File.cwd!/0

也有一些非成对函数,没有非感叹号变体。感叹号仍然表示它会在失败时引发异常。例如:Protocol.assert_protocol!/1

在宏代码中,alias!/1var!/2 上的感叹号表示 宏卫生 被搁置。

尾部问号 (foo?)

返回布尔值的函数以尾部问号命名。

示例:Keyword.keyword?/1Mix.debug?/0String.contains?/2

但是,在守卫中有效的返回布尔值的函数遵循另一个约定,将在下一节中介绍。

is_ 前缀 (is_foo)

类型检查和其他在守卫子句中允许的布尔检查以 is_ 前缀命名。

示例:Integer.is_even/1is_list/1

这些函数和宏遵循 Erlang 的约定,使用 is_ 前缀而不是尾部问号,正是为了表明它们在守卫子句中是允许的。

请注意,在守卫子句中无效的类型检查不遵循此约定。例如:Keyword.keyword?/1

特殊名称

某些名称在 Elixir 中具有特定含义。我们将详细介绍这些情况。

length 和 size

当你看到函数名称中的 size 时,这意味着该操作在常数时间内执行(也写为“O(1) 时间”),因为大小与数据结构一起存储。

示例:map_size/1tuple_size/1

当你看到 length 时,该操作在线性时间内执行(“O(n) 时间”),因为必须遍历整个数据结构。

示例:length/1String.length/1

换句话说,使用“size”一词的函数将花费相同的时间,无论数据结构是微小还是巨大。相反,使用“length”一词的函数将随着数据结构大小的增长而花费更多时间。