Elixir 1.6のcode formatterで自動フォーマット


Elixir 1.6もそろそろリリースが視野に見えてきたところですが、1.6の注目機能といえばやはり自動フォーマット機能でしょう。
[gofmt](https://golang.org/cmd/gofmt/)のように自動的にコーディングスタイルを変換してくれます。なんといっても言語公式のフォーマッタなので無駄な宗教戦争が起きることもなく平和的にコーディングスタイルを解決出来るし、コードレビューの手間も少なくなりいいことづくめですね。

2017-10-26現在ElixirのGitHub masterブランチをインストールすれば試すことが出来るので試してみます。

## Environment

- Elixir 1.6.0-dev (2017-10-26現在)

## Installation

手っ取り早く1.6.0-devを試すために[kiex](https://github.com/taylor/kiex)(Elixirのバージョンマネージャ)をインストールします

```bash
$ curl -sSL https://raw.githubusercontent.com/taylor/kiex/master/install | bash -s
```

`.bashrc`や`.zshrc`でkiexを読み込むように以下を追加

```bash
test -s "$HOME/.kiex/scripts/kiex" && source "$HOME/.kiex/scripts/kiex
```

masterブランチの1.6.0-devをインストールします

```bash
$ kiex install master
```

```
$ kiex use master
```

iexを起動し`1.6.0-dev`がインストールされていることを確認

```bash
$ iex
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.6.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 
```

プロジェクト全体を一括でフォーマットするには`.formatter.exs`を作成し対象のファイルを指定します

```elixir
[
  inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
```

フォーマット

```bash
$ mix format
```

`.formatter.exs`が存在する場合はプロジェクト全体のファイルがフォーマットされます。

ファイルがフォーマット済みかどうかは`--check-formatted`オプションで確認出来ます
フォーマット済みでないファイルがある場合は以下のようにエラーが返ります

```bash
$ mix format --check-formatted 
** (Mix) mix format failed due to --check-formatted.
The following files were not formatted:

  * config/config.exs

$ echo $?
1

```

フォーマット済みでないファイルがある場合はCIで落とすようにすれば、コードレビューでのコーディングスタイルの指摘を省略して本質的な部分に集中出来ますね

### フォーマッタの気持ちを察する

最初はフォーマッタにかけると複数行になったり逆に見辛くなる箇所があります。
そういうところはフォーマッタの気持ちを察してフォーマット後も見やすくなるように書きなおすと案外構造が明確になって綺麗になったりします。
この点に関してElixir作者のJoséがリファクタリングの例を以下のissueで説明しているので簡単に詳解します
https://github.com/elixir-lang/elixir/issues/6643

#### 複数行のデータ構造

例えば以下のようなコードをフォーマッタにかけると

```elixir
:ets.insert(table, {doc_tuple, line, kind,
                    merge_signatures(current_sign, signature, 1),
                    if(is_nil(doc), do: current_doc, else: doc)})
```

以下のように変換されます。

```elixir
:ets.insert(table, {
  doc_tuple,
  line,
  kind,
  merge_signatures(current_sign, signature, 1),
  if(is_nil(doc), do: current_doc, else: doc)
})
```

これは以下のようにインラインで実行している関数を外に出せば以下のように書けフォーマットにかけても変わりません

```elixir
new_signature = merge_signatures(current_sign, signature, 1)
new_doc = if is_nil(doc), do: current_doc, else: doc
:ets.insert(table, {doc_tuple, line, kind, new_signature, new_doc})
```

結果的にインラインで実行されている関数を外部で実行することで構造が明確になったと思います

#### 複数行の関数定義

例えば以下のようなコードをフォーマッタにかけると

```elixir
def handle_call({:clean_up_process_data, parent_pid, child_pid}, _from,
                %{parent_pids: parent_pids, child_pids: child_pids} = state) do
```

以下のように変換されます

```elixir
def handle_call(
      {:clean_up_process_data, parent_pid, child_pid},
      _from,
      %{parent_pids: parent_pids, child_pids: child_pids} = state
    ) do
```

これは関数ヘッダでの変数の展開を以下のように関数内で展開すれば関数定義は一行で済みます

```elixir
def handle_call({:clean_up_process_data, parent_pid, child_pid}, _from, state) do
  %{parent_pids: parent_pids, child_pids: child_pids} = state
```

ヘッダでパターンマッチしたい場合もけっこうありますが１行が長くなりがちで可読性がだんだん悪くなってくるのでフォーマッタで複数行に変換されるレベルなら関数内で展開した方がいいのかもしれません

#### 引数が多い場合

例えば以下のように引数が多い関数を使っている場合

```elixir
assert_raise EEx.SyntaxError,
             "nofile:2: unexpected end of string, expected a closing '<% end %>'",
             fn ->
               EEx.compile_string("foo\n<% if true do %>")
             end
```

以下のように書き直すことでフォーマットされずに済みます

```elixir
message = "nofile:2: unexpected end of string, expected a closing '<% end %>'"

assert_raise EEx.SyntaxError, message, fn ->
  EEx.compile_string("foo\n<% if true do %>")
end
```

`message`に長い文字列リテラルを入れることでより明快なコードになった気がします

### 各エディタ用プラグイン

#### VSCode

- https://marketplace.visualstudio.com/items?itemName=sammkj.vscode-elixir-formatter

#### Atom

- https://atom.io/packages/atom-elixir-formatter

### まとめ

Elixir 1.6のフォーマット機能を簡単に使ってみました。
フォーマット後のコードが複数行になるなどしてくるとそれだけコードが複雑化してきているというコードスメルを可視化出来るという意味でフォーマッタは有用な機能だと思いました。
フォーマッタの気持ちを察してフォーマッタにフォーマットされないようなコードを書けるようになりたいですね