Elixir入門 21: デバッグ
Tags: elixir, webdev, tutorial, programming
Published: 2019-02-12 01:38:06

---
title: Elixir入門 21: デバッグ
published: true
description: Elixir公式サイトの許諾を得て「Debugging」の解説にもとづき、加筆補正を加えて、Elixirでよく行われるデバッグのやり方についてご説明します。
tags: #elixir, #webdev, #tutorial, #programming
---

本稿はElixir公式サイトの許諾を得て「[Debugging](https://elixir-lang.org/getting-started/debugging.html)」の解説にもとづき、加筆補正を加えて、Elixirでよく行われるデバッグのやり方についてご説明します。


# IO.inspect/2
[`IO.inspect/2`](https://hexdocs.pm/elixir/IO.html#inspect/2)は、もとのコードの動きは変えることなく、第1引数`item`を返すので、デバッグに用いると便利です。

```elixir
inspect(item, opts \\ [])
```

たとえば、つぎのようにパイプ演算子`|>`で続く処理の間に`IO.inspect/2`を挟んで、値がどのように変わるのか確かめられます。これにより結果が変わることはありません。

```elixir
defmodule Example do
  def square_sum(first, last) do
    for i <- first..last do
      i
    end
    |> IO.inspect
    |> Enum.map(fn x -> x * x end)
    |> IO.inspect
    |> Enum.sum
  end
end
```

```elixir
iex> Example.square_sum(1, 5)
[1, 2, 3, 4, 5]
[1, 4, 9, 16, 25]
55
```

`IO.inspect/2`の第2引数のオプションに`:label`を渡すと、出力する第1引数の項目の前にその文字列が添えられます。よく組み合わせて使われるのが、変数と値のキーワードリストを返す[`binding/0`](https://hexdocs.pm/elixir/Kernel.html#binding/0)です。`IO.inspect/2`に渡せば、関数が呼び出されたときに受け取った引数名と値が確かめられます。

```elixir
defmodule Example do
  def square_sum(first, last) do
    IO.inspect binding()
    for i <- first..last do
      i
    end
    |> IO.inspect(label: "before")
    |> Enum.map(fn x -> x * x end)
    |> IO.inspect(label: "after")
    |> Enum.sum
  end
end
```

```elixir
iex> Example.square_sum(1, 5)
[first: 1, last: 5]
before: [1, 2, 3, 4, 5]
after: [1, 4, 9, 16, 25]
55
```

`IO.inspect/2`の第2引数にどのようなフォーマット用のオプションが渡せるかについては「[Inspect.Opts](https://hexdocs.pm/elixir/Inspect.Opts.html)」をお読みください。


# IEx.pry/0とIEx.break!/2
`IO.inspect/2`を使うのは静的なデバッグでした。Elixirのインタラクティブシェルには、対話的にコードをデバッグできる動的なやり方もあります。そのひとつは、静的な`IO.inspect binding()`に替わる[`IEx.pry/0`](https://hexdocs.pm/iex/IEx.html#pry/0)です。使う前に必ず[`require/2`](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#require/2)に`IEx`モジュールを与えてください。

```elixir
defmodule Example do
  def square_sum(first, last) do
    require IEx; IEx.pry
    for i <- first..last do
      i
    end
    |> Enum.map(fn x -> x * x end)
    |> Enum.sum
  end
end
```

`iex`セッションで関数を呼び出すと、コードの実行は止まり、`pry`が動きます。IExから変数や`import`あるいはエイリアスなどがすべて参照できるのです。コードの実行を再開して`iex`に戻るには[`respawn/0`](https://hexdocs.pm/iex/IEx.Helpers.html#respawn/0)を呼び出してください。Mixプロジェクトでも、`iex -S mix`で同じようにデバッグできます。

```elixir
iex> Example.square_sum(1, 5)
Break reached: Example.square_sum/2 (example.exs:3)

    1: defmodule Example do
    2:   def square_sum(first, last) do
    3:     require IEx; IEx.pry
    4:     for i <- first..last do
    5:       i

pry> first
1
pry> last
5
pry> respawn

Interactive Elixir (1.6.5) - press Ctrl+C to exit (type h() ENTER for help)
55
```

`IO.inspect/2`も`IEx.pry/0`も、デバッグのためのコードを書き加えなければなりませんでした。[`break!/2`](https://hexdocs.pm/iex/IEx.html#break!/2)を使えば、ソースに手は加えずにIExでブレークポイントの設定や管理ができます。

ただし、コードはコンパイルされていなければなりません。たとえば、つぎのコードが書かれたファイル`example.ex`をコンパイルしましょう(「[Elixir入門 08: モジュールと関数](https://dev.to/gumi/elixir-08--1c4c)」「コンパイル」参照)。

```elixir
defmodule Example do
  def square_sum(first, last) do
    for i <- first..last do
      i
    end
    |> Enum.map(fn x -> x * x end)
    |> Enum.sum
  end
end
```

```terminal
$ elixirc example.ex
```

`iex`セッションで`break!/2`を呼び出すと、引数の関数にブレークポイントが加えられて、`pry`で引数や変数などが参照できます。現在の位置を示すのが[`whereami/1`](https://hexdocs.pm/iex/IEx.Helpers.html#whereami/1)です。引数により表示するコードの範囲が変えられます。

```elixir
iex> break! Example.square_sum/2
1
iex> Example.square_sum(1, 5)
Break reached: Example.square_sum/2 (example.ex:2)

    1: defmodule Example do
    2:   def square_sum(first, last) do
    3:     for i <- first..last do
    4:       i

pry> first
1
pry> last
5
pry> whereami
Location: example.ex:2

    1: defmodule Example do
    2:   def square_sum(first, last) do
    3:     for i <- first..last do
    4:       i

    Example.square_sum/2

pry> whereami 9
Location: example.ex:2

    1: defmodule Example do
    2:   def square_sum(first, last) do
    3:     for i <- first..last do
    4:       i
    5:     end
    6:     |> Enum.map(fn x -> x * x end)
    7:     |> Enum.sum
    8:   end
    9: end

    Example.square_sum/2

pry> respawn

Interactive Elixir (1.6.5) - press Ctrl+C to exit (type h() ENTER for help)
55
```

`break!/2`を使えば、組み込み済み関数の動きも確かめられます(図001およびリンク映像)。たとえば、[`URI.decode_query/2`](https://hexdocs.pm/elixir/URI.html#decode_query/2)は、クエリー文字列をマップにデコードする関数です。

```elixir
decode_query(query, map \\ %{})
```

```elixir
iex> break! URI.decode_query/2
1
iex> URI.decode_query("percent=oh+yes%21", %{"starting" => "map"})
Break reached: URI.decode_query/2 (/private/tmp/elixir-20180507-68757-17gx35t/elixir-1.6.5/lib/elixir/lib/uri.ex:140)
pry> query
"percent=oh+yes%21"
pry> map
%{"starting" => "map"}
pry> respawn

Interactive Elixir (1.6.5) - press Ctrl+C to exit (type h() ENTER for help)
%{"percent" => "oh yes!", "starting" => "map"}
```

#### 図001■break!/2でデバッグする
[![elixir_21_001.png](https://thepracticaldev.s3.amazonaws.com/i/o8fju43zp24pkwenmdon.png)
\>\> 映像へ](https://asciinema.org/a/0h3po0AmTcBAorc5GBNU97nrs)


`break!/2`はソースでなく、コンパイルされたコードをデバッグします。そのため、エイリアスや`import`は参照できません。


# デバッガ
Erlang/OTPにはグラフィカルユーザインタフェースのデバッガ`:debugger`が備わっています。視覚的にブレークポイントを操作したり、変数の値などが調べられるのです(図002)。

#### 図002■グラフィカルユーザインタフェースによる:debuggerの操作
![elixir.png](https://elixir-lang.org/images/contents/debugger-elixir.gif)
「[Debugging techniques in Elixir](http://blog.plataformatec.com.br/2016/04/debugging-techniques-in-elixir-lang/)」より

`iex`シェルでつぎの3つの操作を行うと、デバッガの準備が整います。なお、モジュールはコンパイルされていなければなりません。

- [`debugger.start/0`](http://erlang.org/doc/man/debugger.html#start-0): デバッガを開始して、デバッガのウィンドウを開きます。
- [`int.ni/1`](http://erlang.org/doc/man/int.html#ni-1): デバッグするモジュールを定めます。
- [`int.break/2`](http://erlang.org/doc/man/int.html#break-2): モジュールに加えるブレークポイントの行を指定します。

そのうえでモジュールの関数を呼び出せば、デバッガが使えるようになります。

```elixir
iex> :debugger.start()
{:ok, #PID<0.86.0>}
iex> :int.ni(Example)
{:module, Example}
iex> :int.break(Example, 6)
:ok
iex> Example.square_sum(1, 5)
```

[Monitor]ウィンドウ左上のモジュールをダブルクリックすると、[View Module]のウィンドウが開きます(図003)。

#### 図003■MonitorからView Moduleのウィンドウを開く
![elixir_plug_003.png](https://thepracticaldev.s3.amazonaws.com/i/l9ui753m072nj5jdtmwt.png)
![elixir_plug_004.png](https://thepracticaldev.s3.amazonaws.com/i/76fd48nxg2g471j03tzy.png)

[Break]メニューから[Line Break...]を選んで開く[Line Break]ダイアログボックスで、ブレークポイントの追加や削除などの操作ができます(図004)。

#### 図004■ブレークポイントを操作する
![elixir_plug_006.png](https://thepracticaldev.s3.amazonaws.com/i/7o2fvv4rw8ehf6go5feb.png)

[Monitor]ウィンドウに戻って右側のブレークポイントが入ったプロセスのリストをダブルクリックして、[Attach Process]ウィンドウを開けばプロセスが操作できます(図005)。プロセスを進めると、ウィンドウ下側右の変数の値がどう変わるか確かめられます。変数をクリックして、その時々の変数値が左側に記録できます。

#### 図005■プロセスを操作すると値の変化が確かめられる
![elixir_plug_005.png](https://thepracticaldev.s3.amazonaws.com/i/u7kfmb2brcws05m5c65z.png)
![elixir_plug_007.png](https://thepracticaldev.s3.amazonaws.com/i/kwp5nc7kpb13og90g7of.png)

デバッガの操作について詳しくは、Erlangの「[Debugger](http://erlang.org/doc/apps/debugger/debugger_chapter.html)」をお読みください。


# オブザーバー
複雑なシステムをデバッグするには、コードをたどるだけでは不十分です。仮想マシン全体やプロセスとアプリケーションを理解し、トレースのメカニズムをつくらなければなりません。Erlangにはそのために[`:observer`](http://erlang.org/doc/man/observer.html)が備わっているのです。グラフィカルユーザインタフェースが開かれ、いくつもの画面でランタイムとプロジェクトを把握する手助けとなります(「[Observer](http://erlang.org/doc/apps/observer/observer_ug.html)」参照)。

```elixir
iex> :observer.start()
```

#### 図006■:observerのグラフィカルユーザインタフェース
![elixir_plug_008.png](https://thepracticaldev.s3.amazonaws.com/i/p0t94tpx6pt6xwih89v7.png)


「MIX AND OTP」の「Dynamic supervisors」では、実際のプロジェクトで「[Observer](https://elixir-lang.org/getting-started/mix-otp/dynamic-supervisor.html#observer)」を扱います。また、`:observer`を使えばリモートノードがイントロスペクトできます(「[Tracing and observing your remote node](http://blog.plataformatec.com.br/2016/05/tracing-and-observing-your-remote-node/)」参照)。Phoenixフレームワークが200万の接続を1台のマシンでデバッグした技術です(「[The Road to 2 Million Websocket Connections in Phoenix](https://phoenixframework.org/blog/the-road-to-2-million-websocket-connections)」参照)。

なお、IExで[`runtime_info/0`](https://hexdocs.pm/iex/IEx.Helpers.html#runtime_info/0)を呼び出せば、ランタイム情報が確かめられます。

```elixir
iex> runtime_info

## System and architecture

Elixir version:     1.6.5
OTP version:        20
ERTS version:       9.3.1
Compiled for:       x86_64-apple-darwin17.5.0
Schedulers:         4
Schedulers online:  4

## Memory

Total:              25 MB
Atoms:              379 kB
Binaries:           309 kB
Code:               9611 kB
ETS:                864 kB
Processes:          5666 kB

## Statistics / limits

Uptime:             54 minutes and 7 seconds
Run queue:          0
Atoms:              14407 / 1048576 (1% used)
ETS:                25 / 2053 (1% used)
Ports:              7 / 65536 (0% used)
Processes:          64 / 262144 (0% used)

Showing topics:     [:system, :memory, :limits]
Additional topics:  [:applications]

To view a specific topic call runtime_info(topic)
```


# その他のツールとコミュニティ
Erlang VMが提供するツールはほかにもあります。

- `: observer`に加え、クラッシュダンプを表示する[`:crashdump_viewer`](http://erlang.org/doc/man/crashdump_viewer.html)も備わっています(「[Crashdump Viewer](http://erlang.org/doc/apps/observer/crashdump_ug.html)」参照)。
- OSレベルのトレーサーとの統合
    - Linux Trace Toolkit(「[LTTng and Erlang/OTP](http://erlang.org/doc/apps/runtime_tools/LTTng.html)」参照)
    - DTrace(「[DTrace and Erlang/OTP](http://erlang.org/doc/apps/runtime_tools/DTRACE.html)」参照)
    - SystemTap(「[SystemTap and Erlang/OTP](http://erlang.org/doc/apps/runtime_tools/SYSTEMTAP.html)」参照)
- マイクロステートアカウンティングは、短い時間間隔で複数の低レベルタスクでランタイムが費やす時間を測定します(「[msacc](http://erlang.org/doc/man/msacc.html)」参照)。
- Mixには`profile`の名前空間に[`mix profile.cprof`](https://hexdocs.pm/mix/Mix.Tasks.Profile.Cprof.html)や[`mix profile.cprof `](https://hexdocs.pm/mix/Mix.Tasks.Profile.Cprof.html)などのタスクが備わっています。
- その他

コミュニティがつくった製品化や開発に役立つツールもあります。

- [Wobserver](https://github.com/shinyscorpion/wObserver): webインタフェースでプロダクションノードを監視します。
- [Visualixir](https://github.com/koudelka/visualixir): 開発時のプロセスメッセージを視覚化します。
- [erlyberly](https://github.com/andytill/erlyberly): 開発時にトレースするためのGUIです。


#### Elixir入門もくじ
- [Elixir入門 01: コードを書いて試してみる](https://dev.to/gumi/elixir-01--2585)
- [Elixir入門 02: 型の基本](https://dev.to/gumi/elixir-02--30n1)
- [Elixir入門 03: 演算子の基本](https://dev.to/gumi/elixir-03--33im)
- [Elixir入門 04: パターンマッチング](https://dev.to/gumi/elixir-04--1346)
- [Elixir入門 05: 条件 - case/cond/if](https://dev.to/gumi/elixir-05----casecondif-60o)
- [Elixir入門 06: バイナリと文字列および文字リスト](https://dev.to/gumi/elixir-06--35na)
- [Elixir入門 07: キーワードリストとマップ](https://dev.to/gumi/elixir-07--39hi)
- [Elixir入門 08: モジュールと関数](https://dev.to/gumi/elixir-08--1c4c)
- [Elixir入門 09: 再帰](https://dev.to/gumi/elixir-09--1a0p)
- [Elixir入門 10: EnumとStream](https://dev.to/gumi/elixir-10-enumstream-4fpb)
- [Elixir入門 11: プロセス](https://dev.to/gumi/elixir-11--2mia)
- [Elixir入門 12: 入出力とファイルシステム](https://dev.to/gumi/elixir-12--4og6)
- [Elixir入門 13: aliasとrequireおよびimport](https://dev.to/gumi/elixir-13-aliasrequireimport-55c1)
- [Elixir入門 14: モジュールの属性](https://dev.to/gumi/elixir-14--3511)
- [Elixir入門 15: 構造体](https://dev.to/gumi/elixir-15--4f43)
- [Elixir入門 16: プロトコル](https://dev.to/gumi/elixir-16--lif)
- [Elixir入門 17: 内包表記](https://dev.to/gumi/elixir-17--5gci)
- [Elixir入門 18: シギル](https://dev.to/gumi/elixir-18--5791)
- [Elixir入門 19: tryとcatchおよびrescue](https://dev.to/gumi/elixir-19-trycatchrescue-50i8)
- [Elixir入門 20: 型の仕様とビヘイビア](https://dev.to/gumi/elixir-20--j50)
- Elixir入門 21: デバッグ
- [Elixir入門 22: Erlangライブラリ](https://dev.to/gumi/elixir-22-erlang-2492)
- [Elixir入門 23: つぎのステップ](https://dev.to/gumi/elixir-23--50ik)

##### 番外
- [Elixir入門: Plugを使うには](https://dev.to/gumi/elixir-plug-40lb)