感動したPull Request: Elixirにおけるテスト，リファクタリング，ドキュメント，PRの書き方のお手本

Pelemay Backendという Apache 2.0 License のOSSを開発しています．最初の小さなリリース ~~に向けて鋭意開発を進めています．~~ は2023年7月6日にできました！

https://github.com/zeam-vm/pelemay_backend

https://hex.pm/packages/node_activator

このPelemay Backendのコンセプトは次の記事にまとめています．

https://zacky1972.github.io/blog/2023/05/26/pelemay_backend.html

一言で言うと，次のようなものです．

> PelemayBackend: A memory-saving, fault-tolerant and distributed collection of Nx compilers and backends for embedded systems.
> PelemayBackend: 組込みシステム用の Nx コンパイラとバックエンドの省メモリフォールトトレラント分散型コレクション

今は @mnishiguchi さんと一緒に開発を進めています．

https://qiita.com/mnishiguchi

ElixirのIoTフレームワークである，Nerves Projectのコアメンバーの1人です．

https://nerves-project.org

はじめてZoomでお話しして，Pelemay Backendの開発にお招きしたときに， @mnishiguchi さんは謙遜だと思うのですが，「私はプログラミングは苦手で，テストとドキュメントくらいしか書けない」というような趣旨のことをおっしゃっていました．

しかし，本日いただいたPull Requestを見て，私は驚嘆しました．

https://github.com/zeam-vm/pelemay_backend/pull/146

これは，テスト，リファクタリング，ドキュメント，PRの書き方のお手本にしたいPRだったのです！ これで，プログラミングが苦手って，謙遜も謙遜，なかなかここまで書ける人いないと思いますよ．

この記事では，このお手本PRについて，私の視点で解説していきたいです．

## PR の SubjectとDescriptionが簡潔ながら要点を得ていて，とても読みやすい！

まず目が行くのが，SubjectとDescriptionです．

> refactor(NodeActivator): split module #146
>
> # Description
> 
> * factor out sub-modules
>     * Epmd
>     * Utils
> * add module document for the top-level module
> * add function documents
> 
> # Notes
> 
> The sub-modules are private (@moduledoc false) as of now. Only the top-level module will show in the Hex document.

うーん美しい．このPRの目的と意図が明確に書かれていて，あとで読み返したときに意思決定や思想，文化を理解するのに十分な情報です．

## 思い切ったリファクタリングの方針

次に，先ほどのDescriptionの記述を見返しながら，具体的にどのような変更を加えたのか見てみましょう．

https://github.com/zeam-vm/pelemay_backend/pull/146/files

リファクタリングの方針として，メインモジュールである `NodeActivator`には，思い切って `run`関数のみを置いています．`start_distributed_node`関数は，`defp`と書いてあるように，プライベート関数なので，公開した時のドキュメントに記載されることはありません．

`mix docs` コマンドで生成される`NodeActivator`モジュールのドキュメントを見てみると，次のようにシンプルです．

![Docs of NodeActivator](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55223/efb2e5e1-66f1-db69-8c21-503b343e4980.png)

ごちゃごちゃ定義されていた関数群は，サブモジュール`Epmd`と`Utils`に機能的に分類されて配置されています．しかも，これらのサブモジュールについて，`@moduledoc false`を指定することで，ドキュメントを生成しないように隠蔽しています．

## 的確なドキュメント

次に，`@doc`に書かれた`run`関数の説明を見てみましょう．

> `run(node_name_prefix)`
> `@spec run(binary()) :: {:ok, node()} | {:error, any()}`
>
> Turns a non-distributed node into a distributed node after ensuring that the `epmd` operation system process is running. Returns the name of the started distributed node. The node name will be generated by appending a random string to the provided `node_name_prefix` string.
>
> This function does nothing when the distribution has already been started.
> 
> For more info, see [Node](https://hexdocs.pm/elixir/Node.html).
>
> ## Examples
> `{:ok, node_name} = NodeActivator.run("foo")`

あえて，Google翻訳での日本語訳をそのまま載せます．

> `epmd` オペレーティング システム プロセスが実行されていることを確認した後、非分散ノードを分散ノードに切り替えます。 開始された分散ノードの名前を返します。 ノード名は、指定された `node_name_prefix` 文字列にランダムな文字列を追加することによって生成されます。
>
> すでに配信が開始されている場合、この関数は何も行いません。
>
> 詳細については、[ノード](https://hexdocs.pm/elixir/Node.html) を参照してください。

誤訳がほぼないですね．ErlangやElixirに慣れていない人にとっては，`epmd`が何なのか，という点だけわからないと思いますが，それ以外については，どういう条件の時に何を行うのか，何を返すのかが明確です．また，引数に`node_name_prefix`と命名しているのもポイント高いです．これにより，この文字列を接頭辞(prefix)として用いてノードを命名することが明らかです．

Examplesに載せているコード例に，`iex`をつけないことで，doctestとしてではなく，単なるサンプルコードとして見せている点にも注目です．こうすることで，実行するたびに毎回結果が異なるような，doctestに不向きのコード例を載せることができます．

## テストの機能的な分類

次にテストコードを見てみましょう．

https://github.com/zeam-vm/pelemay_backend/blob/b7733aa528b97ef8c2ae65e1040f4db9843722a0/utilities/node_activator/test/node_activator/utls_test.exs

`describe`で機能面での分類を，`test`でそれぞれの機能に対するテストケースを記述しています．それぞれに書かれている名前も要点を簡潔に記述しています．Elixirにおけるテストケースの見本になっています．

`assert` の対義語である `refute`をうまく使っています．`refute`を使わないと，例えば，次のように書いてしまいがちなところです． 

```elixir
    test "generates unique atom" do
      node_name1 = Utils.generate_node_name("test prefix")
      node_name2 = Utils.generate_node_name("test prefix")

      assert is_atom(node_name1)
      assert node_name1 != node_name2
    end
```

しかし，`refute`を使って次のように書くのが推奨です．

```elixir
    test "generates unique atom" do
      node_name1 = Utils.generate_node_name("test prefix")
      node_name2 = Utils.generate_node_name("test prefix")

      assert is_atom(node_name1)
      refute node_name1 == node_name2
    end
```

## おわりに

Elixir中級者の人は，以上のようなポイントを押さえて，テスト，リファクタリング，ドキュメント，PRを書くと良いと思います．

@mnishiguchi さんがNervesコアメンバーに入っていることの理由の1つが，またひとつ，明らかになりました．