查看源代码 命名约定
本文档是 Elixir 命名约定的参考,涵盖从大小写到标点符号的各个方面。
命名约定本质上是 Elixir 语法的一个子集。约定旨在遵循并设定语言和社区的最佳实践。如果你想要了解 Elixir 语法的完整参考,而不仅仅是约定,请参阅 语法参考。
大小写
Elixir 开发人员在定义变量、函数名称、模块属性等时必须使用 snake_case
。
some_map = %{this_is_a_key: "and a value"}
is_map(some_map)
别名通常用作模块名称,它们是例外,必须大写并使用 CamelCase
,例如 OptionParser
。对于别名,首字母缩略词中的大写字母要保留,例如 ExUnit.CaptureIO
或 Mix.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/1
和 File.read!/1
。 File.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/2
和 Base.decode16!/2
、File.cwd/0
和 File.cwd!/0
。
也有一些非成对函数,没有非感叹号变体。感叹号仍然表示它会在失败时引发异常。例如:Protocol.assert_protocol!/1
。
在宏代码中,alias!/1
和 var!/2
上的感叹号表示 宏卫生 被搁置。
尾部问号 (foo?
)
返回布尔值的函数以尾部问号命名。
示例:Keyword.keyword?/1
、Mix.debug?/0
、String.contains?/2
但是,在守卫中有效的返回布尔值的函数遵循另一个约定,将在下一节中介绍。
is_
前缀 (is_foo
)
类型检查和其他在守卫子句中允许的布尔检查以 is_
前缀命名。
示例:Integer.is_even/1
、is_list/1
这些函数和宏遵循 Erlang 的约定,使用 is_
前缀而不是尾部问号,正是为了表明它们在守卫子句中是允许的。
请注意,在守卫子句中无效的类型检查不遵循此约定。例如:Keyword.keyword?/1
。
特殊名称
某些名称在 Elixir 中具有特定含义。我们将详细介绍这些情况。
length 和 size
当你看到函数名称中的 size
时,这意味着该操作在常数时间内执行(也写为“O(1) 时间”),因为大小与数据结构一起存储。
当你看到 length
时,该操作在线性时间内执行(“O(n) 时间”),因为必须遍历整个数据结构。
换句话说,使用“size”一词的函数将花费相同的时间,无论数据结构是微小还是巨大。相反,使用“length”一词的函数将随着数据结构大小的增长而花费更多时间。