Elixir hello worldから真面目にドキュメントを書く

Elixirをやろうとしていつも挫折をします。Hello, Worldを何度もやった記憶があるのですが、
今回は真面目にExDocを使ってHello, Worldのドキュメントを作ってみます。

# 準備
docker-composeを使ってElixirを使える環境を整えます。

```yaml:docker-compose.yml
version: "3.7"
services:
  web:
    build: .
    working_dir: /opt/app
    volumes:
      - .:/opt/app
```

```dockerfile:Dockerfile
FROM elixir:1.7.4-alpine

RUN mix local.hex --force
```
最新版のElixirのイメージを指定して、docker-compose buildをして最低限の環境を作ります。

# mixコマンドでのプロジェクト作成
以下のコマンドを実行してプロジェクトを生成します。

```
$ docker-compose run --rm web mix new hello_world
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/hello_world.ex
* creating test
* creating test/test_helper.exs
* creating test/hello_world_test.exs

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

    cd hello_world
    mix test

Run "mix help" for more commands.
```

rubyのコマンドでいうところのbundle initのようなものですが、現在いるディレクトリにプロジェクトを
作成するオプションはなく、必ず１回層下のディレクトリが作成されるようです。
docker-compose.ymlのvolumeの部分を新しく作成されたディレクトリに合わせます。

```
    volumes:
      - ./hello_world:/opt/app
```
先程mix newしたときにmix testでテストは実行できると示されたので、mix testを実行して
念の為結果を確認します。

```
$ docker-compose run --rm web mix test
Compiling 1 file (.ex)
Generated hello_world app
..

Finished in 0.1 seconds
1 doctest, 1 test, 0 failures

Randomized with seed 211440
```

# ドキュメントの修正

mix newで生成されたHelloWorldモジュールに既にドキュメントのサンプルが記載されていますので、
これを修正しながら動作確認をしていきます。とりあえず、Elixir 1.7から[ドキュメントに対していくつか
のメタデータを付与出来るようになった](https://elixir-lang.org/blog/2018/07/25/elixir-v1-7-0-released/)ので、メタデータを付加してみます。

```elixir:lib/hello_world.ex
defmodule HelloWorld do
  @moduledoc """
  Documentation for HelloWorld.
  """
  @moduledoc authors: ["d511805"], since: "1.7.0"

  @doc """
  Hello world.

  ## Examples

      iex> HelloWorld.hello()
      :world

  """
  def hello do
    :world
  end
end
```

この状態でIExで確認してみるとメタデータが表示されることが確認できます。
authorsを確認することは出来ないようです。sinceは@doc単位でも記述することが出来ます。

```
iex(4)> h HelloWorld

                                   HelloWorld

since: 1.7.4

Documentation for HelloWorld.
```

# ドキュメントの生成
IEx上ではhでモジュールやそのメソッドのドキュメントを見ることが出来ますが、[ex_doc](https://github.com/elixir-lang/ex_doc)を使うことでhtmlやepubにドキュメントを出力することが出来ます。
mix.esxのdepsに以下の記述を追加して、mix deps getsを行います。

```elixir:mix.esx
  def deps do
    [
      {:ex_doc, "~> 0.19", only: :dev, runtime: false},
    ]
  end
```

```
$ docker-compose run --rm web mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
New:
  earmark 1.3.1
  ex_doc 0.19.2
  makeup 0.8.0
  makeup_elixir 0.13.0
  nimble_parsec 0.5.0
* Getting ex_doc (Hex package)
* Getting earmark (Hex package)
* Getting makeup_elixir (Hex package)
* Getting makeup (Hex package)
* Getting nimble_parsec (Hex package)
```
あとはmix docsを実行すると、docディレクトリにドキュメントが出力されます。

```
$ docker-compose run --rm web mix docs
==> nimble_parsec
Compiling 4 files (.ex)
Generated nimble_parsec app
==> makeup
Compiling 45 files (.ex)
Generated makeup app
==> earmark
Compiling 1 file (.yrl)
Compiling 2 files (.xrl)
Compiling 3 files (.erl)
Compiling 25 files (.ex)
Generated earmark app
==> makeup_elixir
Compiling 4 files (.ex)
Generated makeup_elixir app
==> ex_doc
Compiling 18 files (.ex)
Generated ex_doc app
==> hello_world
Compiling 1 file (.ex)
Generated hello_world app
Docs successfully generated.
View them at "doc/index.html".
```

##メソッドの追加とドキュメントの追加

ドキュメントの出力が完了したので、もう一つ簡単なメソッドを追加して、そのドキュメントを
記述します。

```elixir:lib/hello_world.ex
defmodule HelloWorld do
  ...
  @doc """
  名前をメソッドに入力するとその名前に挨拶をしてくれるメソッドです。
  
  ## パラメータ

    - name: 人名を表現する文字です。

  ## Examples

      iex> HelloWorld.hello()
      :world

  """
  @spec hello(String.t()) :: String.t()
  def hello(name) do
    "Hello, " <> name
  end
end
```
このようなメソッドとそれに対するドキュメントを追加して、IExで確認すると、新しい関数に対するドキュメント
を確認することが出来ます。IExではr　HelloWorldというように編集したモジュールをリロードする機能
がありますが、ドキュメント部分に関しては、rでリロードしても読み込まれずに、IExを再起動する必要が
あるようです。

```
iex(4)> h HelloWorld.hello/1

                                def hello(name)

  @spec hello(String.t()) :: String.t()

名前をメソッドに入力するとその名前に挨拶をしてくれるメソッドです。

## パラメータ

  • name: 人名を表現する文字です。

## Examples

    iex> HelloWorld.hello()
    :world
```

## テストの追加とdoctestの追加
最後に追加したメソッドのtestを追加するのと、doctestの修正も行います。

```elixir:test/hello_world_test.ex
defmodule HelloWorldTest do
  use ExUnit.Case
  doctest HelloWorld
  ...
  test "greets Hello, name" do
    assert HelloWorld.hello("dd511805") == "Hello, dd511805"
  end

end
```
```elixir:lib/hello_world.ex
defmodule HelloWorld do
  ...
  ## Examples

      iex> HelloWorld.hello("dd511805")
      "Hello, dd511805"

      iex> HelloWorld.hello("john")
      "Hello, john"
  """
  @spec hello(String.t()) :: String.t()
  def hello(name) do
    "Hello, " <> name
  end
end
```
テストを流して、動作確認を行います。

```
$ docker-compose run --rm web mix test
Compiling 1 file (.ex)
....

Finished in 0.2 seconds
2 doctests, 2 tests, 0 failures

Randomized with seed 152034
```
無事にテストとdoctestが通っていることが確認出来ました。doctestはドキュメント内に書いてある
テストコードと結果をテストしてくれるものですが、そのコメントに対応するメソッドのテストコードか
まではチェックしていないので、他のメソッドからドキュメントをコピーしている場合などは、正しいサンプル
になっていない場合でもdoctestが通ってしまいます。
それでもメソッドの処理を変更した際にテストだけでなく、ドキュメント側の修正もしないとエラーに
なるのは、テスト、ドキュメント整備の習慣としてはいいことだと思います。