デバッグ

この和訳は最新です。

バグはあらゆるプロジェクトにおいて存在するものであり、それゆえに私たちはデバッグを必要とします。 このレッスンでは、潜在的なバグを見つけ出すための静的解析ツールとともにElixirのコードのデバッグについて学びます。

目次

Dialyxir と Dialyzer

DialyzerDIscrepancy AnaLYZer for ERlang programs は、静的コード解析のためのツールです。 言い換えると、これはコードを 読む だけで 実行 はせず、その内容を解析し、 例えばバグやデッドコード、不要なコード、あるいは到達不能コードを検出します。

Dialyxir は、ElixirにおけるDialyzerの使用を簡易化するmixタスクです。

Dialyzerのような仕様ヘルプツールは、あなたのコードをよりよく理解します。 人が読むことができるドキュメント(それが存在し、良く書かれていれば)とは違い、@specはツールが理解しやすい形式的な文法を使います。

Dialyxirをプロジェクトに追加してみましょう。 最もシンプルな方法はmix.exsファイルに依存を追加することです。

defp deps do
  [{:dialyxir, "~> 0.4", only: [:dev]}]
end

そして以下のコマンドを実行します:

$ mix deps.get
...
$ mix deps.compile

最初のコマンドはDialyxirをダウンロードしてインストールします。 Hexを一緒にインストールするかどうか確認されるかもしれません。 2つ目はDialyxirアプリケーションをコンパイルします。 Dialyxirをグローバルにインストールしたい場合は、 documentation を読んでください。

最後のステップはPLT(Persistent Lookup Table)をリビルドするためにDialyzerを実行することです。 新しいバージョンのErlangやElixirをインストールした際は毎回この作業を行う必要があります。 幸いにも、Dialyzerはあなたが使おうとしている標準ライブラリを毎回解析しません。 ダウンロードが完了するまでには数分かかります。

$ mix dialyzer --plt
Starting PLT Core Build ...
this will take awhile
dialyzer --build_plt --output_plt /.dialyxir_core_18_1.3.2.plt --apps erts kernel stdlib crypto public_key -r /Elixir/lib/elixir/../eex/ebin /Elixir/lib/elixir/../elixir/ebin /Elixir/lib/elixir/../ex_unit/ebin /Elixir/lib/elixir/../iex/ebin /Elixir/lib/elixir/../logger/ebin /Elixir/lib/elixir/../mix/ebin
  Creating PLT /.dialyxir_core_18_1.3.2.plt ...
...
 done in 5m14.67s
done (warnings were emitted)

コードの静的解析

これでDialyxirを使う準備が整いました:

$ mix dialyzer
...
examples.ex:3: Invalid type specification for function 'Elixir.Examples':sum_times/1.
The success typing is (_) -> number()
...

このDialyzerのメッセージの内容は明らかです。sum_times/1関数の戻り値の型が定義されたものと異なります。 これはEnum.sum/1integerではなくnumberを返すためですが、sum_times/1の戻り値の型はintegerとなっています。

numberintegerではないため、このようなエラーとなります。 どのように修正すればよいでしょう?ここではnumberintegerに変換するためにround/1関数を使う必要があります:

@spec sum_times(integer) :: integer
def sum_times(a) do
  [1, 2, 3]
  |> Enum.map(fn el -> el * a end)
  |> Enum.sum()
  |> round
end

最終的に次のようになります:

$ mix dialyzer
...
  Proceeding with analysis...
done in 0m0.95s
done (passed successfully)

静的コード解析のためにツールで仕様を利用すると、自己テストされたバグの少ないコードを作成できます。

デバッグ

時には静的解析だけでは不十分なことがあります。 バグを見つけるために実行フローを理解する必要があるかもしれません。 最も簡単な方法は、値とコードフローを追跡するためにIO.puts/2のような出力ステートメントをコードの中に設置することですが、このテクニックは原始的であり限界があります。 ありがたいことに、ElixirのコードをデバッグするためにErlangデバッガを使用することができます。

基本的なモジュールを見てみましょう:

defmodule Example do
  def cpu_burns(a, b, c) do
    x = a * 2
    y = b * 3
    z = c * 5

    x + y + z
  end
end

ここでiexを実行します:

$ iex -S mix

そしてデバッガを実行します:

iex > :debugger.start()
{:ok, #PID<0.307.0>}

Erlangの:debuggerモジュールはデバッガへのアクセスを提供します。 設定をするためにはstart/1を使います:

  • ファイルパスを渡すことで外部の設定ファイルを使うことができます。
  • 引数が:local:globalであれば、デバッガは次のように動作します:
    • :global – デバッガは全ての既知のノードでコードを解釈します。 これはデフォルトの値です。
    • :local – デバッガは現在のノードでのみコードを解釈します。

次のステップは、モジュールをデバッガにアタッチすることです:

iex > :int.ni(Example)
{:module, Example}

:intモジュールはブレークポイントの作成とコードのステップ実行を可能にするインタプリタです。

デバッガを開始すると、次のような新しいウィンドウが表示されます:

Debugger Screenshot 1

モジュールをデバッガにアタッチした後、左にあるメニューが利用可能になります:

Debugger Screenshot 2

ブレークポイントの作成

ブレークポイントは、実行が中断されるコード内のポイントです。 ブレークポイントを作成する方法は2通りあります:

  • コードに:int.break/2を設置する
  • デバッガのUIを使う

IExでブレークポイントの作成を試してみましょう:

iex > :int.break(Example, 8)
:ok

これはExampleモジュールの8行目にブレークポイントを設置します。 これで関数を実行すると:

iex > Example.cpu_burns(1, 1, 1)

IExでのコードの実行は中断され、デバッガウィンドウは次のように表示されます:

Debugger Screenshot 3

ソースコードを含む追加のウィンドウも表示されます:

Debugger Screenshot 4

このウィンドウでは変数の値を確認したり、次の行に進んだり、式の評価を行ったりすることができます ブレークポイントを無効化するには:int.disable_break/2を使うことができます:

iex > :int.disable_break(Example, 8)
:ok

ブレークポイントを再び有効化するために:int.enable_break/2を実行できますし、次のようにブレークポイントを削除できます:

iex > :int.delete_break(Example, 8)
:ok

デバッガウィンドウでも同様の操作が可能です。 トップメニューの BreakLine Break を選択してブレークポイントを設定できます。 コードを含まない行を選択した場合はブレークポイントは無視されますが、デバッガウィンドウには表示されます。 ブレークポイントには次の3種類があります:

  • 行ブレークポイント - 行に到達した際にデバッガは実行を停止します。:int.break/2で設定します
  • 条件ブレークポイント - 行ブレークポイントと似ていますが、設定された条件を満たした時のみデバッガが停止します。これらは:int.get_binding/2で設定します
  • 関数ブレークポイント - デバッガは関数の先頭の行で停止します。:int.break_in/3で設定します

以上です!デバッグを楽しんでください!