Do you want to pick up from where you left of?
Take me there

文档模块

Documenting Elixir code.

注解

如何写注释以及如何编写高质量的文档,这两个问题在编程界依然存在很多争论。然而,在代码中加入适当的文档或者注释很重要,这点是没有什么争议的。

Elixir把文档看作是一等公民,它提供了大量的函数来为项目创建和操作(access)文档。Elixir提供了多种方式来编写注释或者是注解。下面是其中三种方式:

单行注释

最简单的注释代码的方式可能是使用单行注释了。与Ruby或Python类似,Elixir的单行注释标识符为#,也就是井号。

看下这个Elixir Script (greeting.exs):

# Outputs 'Hello, chum.' to the console.
IO.puts("Hello, " <> "chum.")

对于每一行,Elixir会忽略#到行末的所有内容。这种注释不会影响到程序的执行,但是别人在阅读代码时它可能不会很容易地被看到。因此不要滥用单行注释。乱用注释可能成为别人的噩梦。适度使用就很好。

模块注释

@moduledoc提供模块级别的注释。这个注解一般放在模块定义的最前面,也就是defmodule后。下面的例子简单地展示了@moduledoc这个装饰器的用法。

defmodule Greeter do
  @moduledoc """
  Provides a function `hello/1` to greet a human
  """

  def hello(name) do
    "Hello, " <> name
  end
end

用户可以在IEx里面通过h这个辅助函数看到我们在这个模块里定义的文档。

我们可以通过把 Greeter 模块移到一个新文件 greeter.ex 并编译来亲自试验一下。

iex> c("greeter.ex", ".")
[Greeter]

iex> h Greeter

                Greeter

Provides a function hello/1 to greet a human

注意: 如果代码是在一个 mix 项目底下,我们并不需要像上面那样手动编译文件。只需要通过执行 iex -S mix 命令,IEx 控制台就可以加载当前项目。

函数注释

正如Elixir提供的模块级别的注释,它也为函数级别的注释提供了注释功能。@doc这个装饰器能够提供函数级别的文档注释。@doc装饰器使用的时候只需要放在函数定义前。

defmodule Greeter do
  @moduledoc """
  ...
  """

  @doc """
  Prints a hello message

  ## Parameters

    - name: String that represents the name of the person.

  ## Examples

      iex> Greeter.hello("Sean")
      "Hello, Sean"

      iex> Greeter.hello("pete")
      "Hello, pete"

  """
  @spec hello(String.t()) :: String.t()
  def hello(name) do
    "Hello, " <> name
  end
end

如果在IEx中输入这个函数并且带有-h参数时(别忘了输入模块名),你将会看到下列结果:

iex> c("greeter.ex")
[Greeter]

iex> h Greeter.hello

                def hello(name)

Prints a hello message

Parameters

   name: String that represents the name of the person.

Examples

    iex> Greeter.hello("Sean")
    "Hello, Sean"

    iex> Greeter.hello("pete")
    "Hello, pete"

iex>

注意到我们是如何使用文档标记以及终端是怎么渲染这些文档的了么?这很不错吧,当你看到使用ExDoc模块能够动态地生成HTML文档时,你会觉得更有趣。

注意:@spec这个注解一般用于代码的静态分析。想了解更多的话,可以看下Specifications and types这个章节。

ExDoc

ExDoc是Elixir的官方项目,你可以在 GitHub上看到它。它用于给Elixir项目提供在线HTML文档。首先,先让我们用Mix创建一个新项目:

$ mix new greet_everyone

* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/greet_everyone.ex
* creating test
* creating test/test_helper.exs
* creating test/greet_everyone_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd greet_everyone
    mix test

Run "mix help" for more commands.

$ cd greet_everyone

现在将@doc章节的lib/greeter.ex部分的代码复制粘贴过来,并确认这些代码依然能正常运行。既然我们现在要操作一个Mix项目,那么我们需要使用iex -S mix来在终端中打开这个项目:

iex> h Greeter.hello

                def hello(name)

Prints a hello message

Parameters

  • name: String that represents the name of the person.

Examples

    iex> Greeter.hello("Sean")
    "Hello, Sean"

    iex> Greeter.hello("pete")
    "Hello, pete"

安装过程

假设一切都正常工作,那么你将看到和上面一样的输出,现在我们将配置ExDoc。在文件mix.exs中,添加两个依赖:earmark:ex_doc

  def deps do
    [{:earmark, "~> 1.2", only: :dev},
    {:ex_doc, "~> 0.19", only: :dev}]
  end

使用only: :dev是因为我们不想在生产环境下下载和编译这些依赖。为什么需要Earmark呢?Earmark是一个使用Elixir编写的markdown分析器,ExDoc使用它来将带有@moduledoc@doc的注释转换成漂亮的HTML页面。

你可以不使用Earmark。你可以将分析后端改为Pandoc、Hoedown或者Cmark;但是你得根据这里的文档做一点配置工作。在这篇教程里面,我们将使用Earmark。

生成文档

继续刚才的工作,接下来需要输入两条命令:

$ mix deps.get # gets ExDoc + Earmark.
$ mix docs # makes the documentation.

Docs successfully generated.
View them at "doc/index.html".

如果一切顺利的话,你将会看到熟悉的成功提示消息。让我们看一下项目里面的doc/文件夹。里面有我们生成的文档。如果你使用浏览器打开它的话你将看到如下画面:

ExDoc Screenshot 1

我们可以看到Earmark已经渲染了我们的Markdown注释文档并且ExDoc现在有漂亮的显示格式。

ExDoc Screenshot 2

我们可以将这个文档部署到github,也可以部署到Elixir的官方镜像 HexDocs

最佳实践

编写文档是编程的最佳实践。因为Elixir还很年轻,许多语言标准依然在随着它的生态发展而发展。Elixir的社区正在尝试进行这种最佳实践。你可以从Elixir代码风格中了解到类似的最佳实践。

defmodule Greeter do
  @moduledoc """
  This is good documentation.
  """

end
defmodule Greeter do
  @moduledoc false

end
defmodule Greeter do
  @moduledoc """
  ...

  This module also has a `hello/1` function.
  """

  def hello(name) do
    IO.puts("Hello, " <> name)
  end
end
defmodule Greeter do
  @moduledoc """
  ...

  This module also has a `hello/1` function.
  """

  alias Goodbye.bye_bye
  # 就像上面这样...

  def hello(name) do
    IO.puts("Hello, " <> name)
  end
end
defmodule Greeter do
  @moduledoc """
  ...
  """

  @doc """
  Prints a hello message

  ## Parameters

    - name: String that represents the name of the person.

  ## Examples

      iex> Greeter.hello("Sean")
      "Hello, Sean"

      iex> Greeter.hello("pete")
      "Hello, pete"

  """
  @spec hello(String.t()) :: String.t()
  def hello(name) do
    "Hello, " <> name
  end
end
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!