Elixir 将文档视为一等级别类。文档必须易于编写且易于阅读。在本指南中,您将学习如何在 Elixir 中编写文档,涵盖模块属性、样式实践和文档测试等结构。
Markdown
Elixir 文档是使用 Markdown 编写的。网上有很多关于 Markdown 的指南,我们推荐 GitHub 上的指南作为入门指南:基本写作和格式化语法
模块属性
Elixir 中的文档通常附加到模块属性中。让我们看一个例子:
@moduledoc 属性用于向模块添加文档。@doc 用于函数之前,为其提供文档。除了上述属性之外,@typedoc 还可用于将文档附加到作为 typespecs 一部分定义的类型,我们将在后面进行探讨。Elixir 还允许将元数据附加到文档中,方法是将关键字列表传递给 @doc 和朋友。
函数参数
在记录函数时,编译器会推断参数名称。例如:
编译器会将此参数推断为 map。有时推断结果可能不太理想,尤其是当函数包含多个子句,且每次参数都匹配不同的值时。您可以在实现之前的任何时刻仅声明函数头,从而为文档指定正确的名称:
文档元数据
Elixir 允许开发人员将任意元数据附加到文档中。这是通过将关键字列表传递给相关属性(例如 @moduledoc、@typedoc 和 @doc)来完成的。常用的元数据是 :since,它注释了在哪个版本中添加了特定模块、函数、类型或回调,如上例所示。
另一个常见的元数据是 :deprecated,它会在文档中发出警告,解释不鼓励使用它:
请注意,当开发人员调用函数时,:deprecated 键不会发出警告。如果您希望代码也发出警告,则可以使用 @deprecated 属性:
元数据可以有任何键。文档工具通常使用元数据向读者提供更多数据并丰富用户体验。
建议
编写文档时:
-
保持文档的第一段简洁明了,通常只有一行。ExDoc 等工具使用第一行生成摘要。
-
使用模块的全名引用模块。
3.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
我们建议开发人员将示例包含在他们的文档中,通常放在他们自己的 ## 示例标题下。为了确保示例不会过时,Elixir 的测试框架 (ExUnit) 提供了一项名为 doctests 的功能,允许开发人员测试他们文档中的示例。Doctests 的工作原理是从文档中解析出以 iex> 开头的代码示例。您可以在 ExUnit.DocTest 上阅读有关它们的更多信息。
文档 != 代码注释
Elixir 将文档和代码注释视为不同的概念。文档是您与应用程序编程接口 (API) 用户之间的明确的说明书,无论是第三方开发人员、同事还是您未来的自己。如果模块和函数是 API 的一部分,则必须始终对其进行文档化。
代码注释面向阅读代码的开发人员。它们可用于标记改进、留下注释(例如,为什么由于库中的错误而不得不求助于解决方法)等。它们与源代码相关联:您可以完全重写函数并删除所有现有代码注释,并且它将继续保持相同的行为,而不会改变其行为或文档。
由于私有函数无法从外部访问,因此如果私有函数具有 @doc 属性,Elixir 会发出警告并丢弃其内容。但是,您可以像任何其他代码一样向私有函数添加代码注释,我们建议开发人员在他们认为这会为此类代码的读者和维护者添加相关信息时这样做。
总而言之,文档是与 API 用户的说明书,这些用户不一定有权访问源代码,而代码注释则适用于直接与源代码交互的人。通过区分这两个概念,您可以了解和表达有关软件的不同保证。
隐藏内部模块和函数
除了库作为其公共接口的一部分提供的模块和函数之外,库还可以实现不属于其 API 的重要功能。虽然这些模块和函数可以访问,但它们是库内部的,因此不应该为最终用户提供文档。
Elixir 允许开发人员通过设置 @doc false 来隐藏特定函数,或设置 @moduledoc false 来隐藏整个模块,从而方便地隐藏文档中的模块和函数。如果模块被隐藏,您甚至可以记录模块中的函数,但模块本身不会在文档中列出:
如果您不想隐藏整个模块,您可以单独隐藏函数:
但是,请记住 @moduledoc false 或 @doc false 不会将函数设为私有。上面的函数仍然可以作为 MyApp.Sample.add(1, 2) 调用。不仅如此,如果导入了 MyApp.Sample,add/2 函数也将导入到调用者中。出于这些原因,在向函数添加 @doc false 时要小心,而是使用以下两个选项之一:
1.将未记录的函数移动到带有 @moduledoc false 的模块,如 MyApp.Hidden,确保函数不会被意外暴露或导入。请记住,您可以使用 @moduledoc false 隐藏整个模块,同时仍使用 @doc 记录每个函数。工具仍将忽略该模块。
2.函数名称以一个或两个下划线开头,例如 add/2。以下划线开头的函数会自动被视为隐藏,但您也可以明确添加 @doc false。编译器不会导入带有前导下划线的函数,它们会向阅读代码的任何人提示其预期的私有用途。
Code.fetch_docs/1
Elixir 将文档存储在字节码中预定义的块内。加载模块时不会将文档加载到内存中,而是可以使用 Code.fetch_docs/1 函数从磁盘中的字节码中读取文档。缺点是,内存中定义的模块(例如 IEx 中定义的模块)无法访问其文档,因为它们不会将其字节码写入磁盘。