Elixir入門 21: デバッグ

gumitech

gumi TECH

Posted on February 12, 2019

Elixir入門 21: デバッグ

本稿はElixir公式サイトの許諾を得て「Debugging」の解説にもとづき、加筆補正を加えて、Elixirでよく行われるデバッグのやり方についてご説明します。

IO.inspect/2

IO.inspect/2は、もとのコードの動きは変えることなく、第1引数itemを返すので、デバッグに用いると便利です。

inspect(item, opts \\ [])
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode
iex> Example.square_sum(1, 5)
[1, 2, 3, 4, 5]
[1, 4, 9, 16, 25]
55
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode
iex> Example.square_sum(1, 5)
[first: 1, last: 5]
before: [1, 2, 3, 4, 5]
after: [1, 4, 9, 16, 25]
55
Enter fullscreen mode Exit fullscreen mode

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

IEx.pry/0とIEx.break!/2

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

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
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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

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

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
Enter fullscreen mode Exit fullscreen mode
$ elixirc example.ex
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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

decode_query(query, map \\ %{})
Enter fullscreen mode Exit fullscreen mode
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"}
Enter fullscreen mode Exit fullscreen mode

図001■break!/2でデバッグする

elixir_21_001.png

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

デバッガ

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

図002■グラフィカルユーザインタフェースによる:debuggerの操作

elixir.png
Debugging techniques in Elixir」より

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

  • debugger.start/0: デバッガを開始して、デバッガのウィンドウを開きます。
  • int.ni/1: デバッグするモジュールを定めます。
  • int.break/2: モジュールに加えるブレークポイントの行を指定します。

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

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)
Enter fullscreen mode Exit fullscreen mode

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

図003■MonitorからView Moduleのウィンドウを開く

elixir_plug_003.png
elixir_plug_004.png

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

図004■ブレークポイントを操作する

elixir_plug_006.png

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

図005■プロセスを操作すると値の変化が確かめられる

elixir_plug_005.png
elixir_plug_007.png

デバッガの操作について詳しくは、Erlangの「Debugger」をお読みください。

オブザーバー

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

iex> :observer.start()
Enter fullscreen mode Exit fullscreen mode

図006■:observerのグラフィカルユーザインタフェース

elixir_plug_008.png

「MIX AND OTP」の「Dynamic supervisors」では、実際のプロジェクトで「Observer」を扱います。また、:observerを使えばリモートノードがイントロスペクトできます(「Tracing and observing your remote node」参照)。Phoenixフレームワークが200万の接続を1台のマシンでデバッグした技術です(「The Road to 2 Million Websocket Connections in Phoenix」参照)。

なお、IExでruntime_info/0を呼び出せば、ランタイム情報が確かめられます。

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)
Enter fullscreen mode Exit fullscreen mode

その他のツールとコミュニティ

Erlang VMが提供するツールはほかにもあります。

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

  • Wobserver: webインタフェースでプロダクションノードを監視します。
  • Visualixir: 開発時のプロセスメッセージを視覚化します。
  • erlyberly: 開発時にトレースするためのGUIです。

Elixir入門もくじ

番外
💖 💪 🙅 🚩
gumitech
gumi TECH

Posted on February 12, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related