ElixirDay 1

2018年12月版mix newした後に必ずやること

私が $ mix new foo でライブラリを新規作成したあとに毎回必ずやっていること。

2018年12月現在は以下の4つだ。


  • ドキュメント生成ツール ex_doc 追加

  • 静的解析ツール dialyxir 追加(型チェック)

  • 静的解析ツール credo 追加(Lint)

  • フォーマッタで折り返される1行あたりの文字数を120にする


ドキュメント生成ツール ex_doc

ex_doc は Elixir を用いたプロジェクトのドキュメントを生成してくれるライブラリ。それだけでなく Elixir 自身のドキュメントも ex_doc を用いて出力されている。


mix.exs に追加する

diff --git a/mix.exs b/mix.exs

index f6d766a..d18402d 100644
--- a/mix.exs
+++ b/mix.exs
@@ -21,8 +21,7 @@ defmodule Foo.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
- # {:dep_from_hexpm, "~> 0.3.0"},
- # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
+ {:ex_doc, "~> 0.19", only: :dev, runtime: false}
]
end
end


使う

mix deps.getmix deps.compile の両方を行ってからでないと mix docs コマンドが出てこないので注意すること。

$ mix deps.get

$ mix deps.compile
$ mix docs
Compiling 1 file (.ex)
Generated foo app
Docs successfully generated.
View them at "doc/index.html".

doc/index.html を開くと

image.png

このようなドキュメントが表示できる。


静的解析ツール dialyxir

dialyxir は ErlangVM 向け静的解析ツール dialyzer を mix コマンドから簡潔に使えるようにしたもの。


mix.exs に追加する

diff --git a/mix.exs b/mix.exs

index d18402d..5f588ae 100644
--- a/mix.exs
+++ b/mix.exs
@@ -21,7 +21,8 @@ defmodule Foo.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
- {:ex_doc, "~> 0.19", only: :dev, runtime: false}
+ {:ex_doc, "~> 0.19", only: :dev, runtime: false},
+ {:dialyxir, "~> 1.0.0-rc.4", only: :dev, runtime: false}
]
end
end


使う

mix deps.getmix deps.compile の両方を行ってからでないと mix dialyzer コマンドが出てこないので注意すること。

mix dialyzer は初回実行にすごく時間がかかる。これはそういうものなのであきらめよう。2回目以降は1回目に生成したPLTと呼ばれるキャッシュを利用するのでかなり改善される。

$ mix deps.get

$ mix deps.compile
$ time mix dialyzer
Total errors: 0, Skipped: 0
done (passed successfully)
337.91 real 805.19 user 41.86 sys
$ time mix dialyzer
Total errors: 0, Skipped: 0
done in 0m1.02s
done (passed successfully)
1.62 real 1.70 user 0.32 sys

誤りを検出した場合にどう動作するか

lib/foo/foo.ex を以下のように書き換えて試す。add という関数を追加した。

Elixir 界では + が使えるのは number 型のみなのでエラーが検出できるはず。

defmodule Foo do

@moduledoc """
Documentation for Foo.
"""

@doc """
Hello world.

## Examples

iex> Foo.hello()
:world

"""
def hello do
result = add("x", "y")
:world
end

def add(a, b) do
a + b
end
end

$ mix dialyzer

Total errors: 2, Skipped: 0
done in 0m1.18s
lib/foo.ex:15:no_return
Function hello/0 has no local return.
________________________________________________________________________________
lib/foo.ex:16:call
The call:
Foo.add("x", <<_::8>>)

will never return since it differs in arguments with
positions 1st and 2nd from the success typing arguments:

(number(), number())
________________________________________________________________________________
done (warnings were emitted)

Foo.add の引数の型は number()number() であるはずだと言われている。

うまく動作しているようだ。

(lib/foo/foo.ex を元に戻す)


静的解析ツール credo

credo も先程の dialyxir と同様に静的解析ツールだ。ただ着目しているところが異なり credo はコードの一貫性を保つことや、より望ましいコードとなるよう手引きするためにある。いわゆる Lint ツールだ。


mix.exs に追加する

diff --git a/mix.exs b/mix.exs

index 5f588ae..ad6948a 100644
--- a/mix.exs
+++ b/mix.exs
@@ -22,7 +22,8 @@ defmodule Foo.MixProject do
defp deps do
[
{:ex_doc, "~> 0.19", only: :dev, runtime: false},
- {:dialyxir, "~> 1.0.0-rc.4", only: :dev, runtime: false}
+ {:dialyxir, "~> 1.0.0-rc.4", only: :dev, runtime: false},
+ {:credo, "~> 1.0.0", only: :dev, runtime: false}
]
end
end


使う

mix deps.getmix deps.compile の両方を行ってからでないと mix credo コマンドが出てこないので注意すること。

$ mix deps.get

$ mix deps.compile
$ mix credo
Checking 3 source files ...

Please report incorrect results: https://github.com/rrrene/credo/issues

Analysis took 0.1 seconds (0.01s to load, 0.1s running checks)
3 mods/funs, found no issues.

Use `--strict` to show all issues, `--help` for options.

mix credo するとき、チェック設定を .credo.exs というファイルから自動的に読み込んでくれる。ファイルを置ける有効な場所はルートディレクトリか、config/ だ。昨今のルートディレクトリは様々な物が置かれて混雑するので、私は config/ に置いている。

またそのチェック設定のファイルを mix credo.gen.config で生成できる。

そこで生成して config/ に置く。

$ mix credo.gen.config

$ mv .credo.exs config/

望ましくないコードを検出したらどう動くか。

lib/foo/foo.ex を以下のように書き換えて試す。@moduledoc という、モジュールに対するドキュメントを取り去った。

defmodule Foo do

@doc """
Hello world.

## Examples

iex> Foo.hello()
:world

"""
def hello do
:world
end
end

そうすると

$ mix credo

Checking 3 source files ...

Code Readability

[R] → Modules should have a @moduledoc tag.
┃ lib/foo.ex:1:11 #(Foo)

Please report incorrect results: https://github.com/rrrene/credo/issues

Analysis took 0.1 seconds (0.00s to load, 0.1s running checks)
3 mods/funs, found 1 code readability issue.

という形式で望ましくない部分が検出される。

(lib/foo/foo.ex を元に戻す)


フォーマッタで折り返される1行あたりの文字数を120にする

Elixir1.6 からはフォーマッタが標準添付されており mix format でフォーマットできる。デフォルト時に何文字で折り返すかは Code.format_string!/2 に書かれているとおり 98 文字だ。私はこれだと少し短く感じたので 120 文字にしている。120 文字は credo で文字の横幅が長すぎるときに出すデフォルト値でもあるので丁度よい。

1行あたりの文字数、他にどんな候補があるかは Elixirでは行の最大長をいくつにするべきか をみてほしい。

変更するには .formatter.exs に設定を追加すればよい。

diff --git a/.formatter.exs b/.formatter.exs

index d2cda26..0a70dc0 100644
--- a/.formatter.exs
+++ b/.formatter.exs
@@ -1,4 +1,5 @@
# Used by "mix format"
[
- inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
+ inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
+ line_length: 120
]