查看源代码 编写文档
Elixir 将文档视为一等公民。文档必须易于编写且易于阅读。在本指南中,您将学习如何在 Elixir 中编写文档,涵盖模块属性、样式实践和 doctest 等结构。
Markdown
Elixir 文档使用 Markdown 编写。网上有很多关于 Markdown 的指南,我们建议您从 GitHub 的指南入手。
模块属性
Elixir 中的文档通常附加到模块属性。让我们看一个例子
defmodule MyApp.Hello do
@moduledoc """
This is the Hello module.
"""
@moduledoc since: "1.0.0"
@doc """
Says hello to the given `name`.
Returns `:ok`.
## Examples
iex> MyApp.Hello.world(:john)
:ok
"""
@doc since: "1.3.0"
def world(name) do
IO.puts("hello #{name}")
end
end
@moduledoc
属性用于向模块添加文档。 @doc
用于在函数之前提供其文档。除了上述属性外,@typedoc
也可用于将文档附加到类型规范中定义的类型,我们将在后面进行探讨。Elixir 还允许通过将关键字列表传递给 @doc
及其朋友来将元数据附加到文档。
函数参数
在记录函数时,参数名称由编译器推断。例如
def size(%{size: size}) do
size
end
编译器将推断此参数为 map
。有时推断会不理想,尤其是在函数包含多个子句,并且每次参数匹配不同的值时。您可以通过在任何时刻在实现之前仅声明函数头来指定文档的正确名称
def size(map_with_size)
def size(%{size: size}) do
size
end
文档元数据
Elixir 允许开发人员将任意元数据附加到文档。这可以通过将关键字列表传递给相关属性(如 @moduledoc
、@typedoc
和 @doc
)来实现。常用的元数据是 :since
,它用于注释特定模块、函数、类型或回调在哪个版本中添加,如上面的示例所示。
另一个常见的元数据是 :deprecated
,它在文档中发出警告,说明不鼓励使用它
@doc deprecated: "Use Foo.bar/2 instead"
请注意,:deprecated
键不会在开发人员调用函数时发出警告。如果您希望代码也发出警告,可以使用 @deprecated
属性
@deprecated "Use Foo.bar/2 instead"
元数据可以具有任何键。文档工具通常使用元数据为读者提供更多数据,并丰富用户体验。
建议
编写文档时
保持文档的第一段简明扼要,通常是一句话。像 ExDoc 这样的工具使用第一行生成摘要。
按完整名称引用模块。
Markdown 使用反引号 (
`
) 引用代码。Elixir 在此基础上构建,以便在引用模块或函数名称时自动生成链接。因此,始终使用完整模块名称。如果您有一个名为MyApp.Hello
的模块,始终将其引用为`MyApp.Hello`
,而不是`Hello`
。如果函数是本地的,请按名称和元数引用它,例如
`world/1`
,或者如果指向外部模块,请按模块、名称和元数引用它:`MyApp.Hello.world/1`
。通过在前面添加
c:
来引用@callback
,例如`c:world/1`
。通过在前面添加
t:
来引用@type
,例如`t:values/0`
。用二级 Markdown 标题
##
开始新部分。一级标题保留用于模块和函数名称。将文档放在多子句函数的第一个子句之前。文档始终是针对每个函数和元数的,而不是针对每个子句的。
使用文档元数据中的
:since
键来注释何时向 API 添加新函数或模块。
Doctests
我们建议开发人员在其文档中包含示例,通常在他们自己的 ## Examples
标题下。为了确保示例不会过时,Elixir 的测试框架 (ExUnit) 提供了一个名为 doctest 的功能,允许开发人员测试其文档中的示例。Doctests 通过解析从文档中以 iex>
开头的代码样本工作。您可以在 ExUnit.DocTest
中了解更多有关它们的信息。
文档 != 代码注释
Elixir 将文档和代码注释视为不同的概念。文档是您与应用程序编程接口 (API) 用户之间的明确契约,无论它们是第三方开发人员、同事还是未来的您。如果模块和函数是 API 的一部分,则必须始终对其进行记录。
代码注释旨在供阅读代码的开发人员使用。它们对于标记改进、留下笔记(例如,由于库中的错误而不得不求助于变通方法的原因)等非常有用。它们与源代码绑定:您可以完全重写函数并删除所有现有的代码注释,它将继续表现相同,其行为或文档都不会改变。
由于私有函数无法从外部访问,因此如果私有函数具有 @doc
属性,Elixir 将发出警告并丢弃其内容。但是,您可以像对任何其他代码一样,向私有函数添加代码注释,我们建议开发人员在认为这会为此类代码的读者和维护者添加相关信息时这样做。
总之,文档是与 API 用户之间的契约,这些用户可能无法访问源代码,而代码注释是针对与源代码直接交互的人员的。通过将这两个概念分开,您可以了解和表达有关软件的不同保证。
隐藏内部模块和函数
除了库作为其公共接口提供的一部分的模块和函数外,库还可能实现重要的功能,而这些功能不是其 API 的一部分。虽然可以访问这些模块和函数,但它们旨在作为库的内部模块,因此不应为最终用户提供文档。
方便的是,Elixir 允许开发人员通过将 @doc false
设置为隐藏特定函数,或将 @moduledoc false
设置为隐藏整个模块来隐藏文档中的模块和函数。如果隐藏了模块,您甚至可以记录模块中的函数,但模块本身不会在文档中列出
defmodule MyApp.Hidden do
@moduledoc false
@doc """
This function won't be listed in docs.
"""
def function_that_wont_be_listed_in_docs do
# ...
end
end
如果您不想隐藏整个模块,可以单独隐藏函数
defmodule MyApp.Sample do
@doc false
def add(a, b), do: a + b
end
但是,请记住,@moduledoc false
或 @doc false
不会使函数变为私有。上面的函数仍然可以作为 MyApp.Sample.add(1, 2)
调用。不仅如此,如果导入了 MyApp.Sample
,add/2
函数也将被导入到调用方。出于这些原因,在向函数添加 @doc false
时要谨慎,而是使用以下两种选项之一
将未记录的函数移至带有
@moduledoc false
的模块,例如MyApp.Hidden
,确保该函数不会意外公开或导入。请记住,您可以使用@moduledoc false
隐藏整个模块,并仍然使用@doc
记录每个函数。工具将仍然忽略该模块。用一个或两个下划线开头函数名称,例如
__add__/2
。以下划线开头的函数将自动被视为隐藏,尽管您也可以明确地添加@doc false
。编译器不会导入以下划线开头的函数,并且它们会提示任何阅读其预期私有用途代码的人。
Code.fetch_docs/1
Elixir 将文档存储在字节码中的预定义块中。在加载模块时不会将文档加载到内存中,而是可以使用 Code.fetch_docs/1
函数从磁盘上的字节码中读取文档。缺点是,在内存中定义的模块(如在 IEx 中定义的模块)无法访问其文档,因为它们不会将其字节码写入磁盘。