効率よくデバッグする方法

Eclipseデバッガを活用する31のTipsにたくさんのはてブがつきました。たくさんの方に見ていただけたようで、とてもうれしいです。どうもありがとうございました。
デバッガの使い方のスライドを作ってみたものの、効率良くデバッグする方法については書いていませんでした。例えば、ブレークポイントをどこに貼ると効率が良いのか、教えてほしいという声がありました。デバッガ機能をどう使うとより効率的にデバッグできるのか、考えてみました。

デバッグにおける2つのポイント

突然ですが、僕は、デバッグには下記の2つのポイントがあると考えてます。

  • 障害の再現方法を調査する。
  • ソフトウェアの内部状態を調査する。

みなさんはどうやってデバッグしていますか?僕がデバッグを行う時の流れを書いてみると、

  1. 報告された情報を元に、障害がどうやって起きるのか、再現方法を確認します。
  2. 再現方法が報告されていない場合は、再現方法を調査します。
  3. ソフトウェアの内部状態が障害発生時にどうなっているか把握するため、再現方法の調査中、変数などソフトウェアの内部状態も観察します。
  4. 再現方法と障害発生時の内部状態がわかれば、障害が起きる内部状態にならないよう、ソースコードを修正します。
  5. 再現手順を実施して障害が修正されている事を確認します。
  6. 障害の修正により、デグレードが起きていないかを簡単に確認します。

こんな感じでデバッグを行っています。

「障害の再現方法」と「ソフトウェアの内部状態」をデバッグのポイントとして上げたのは、デバッガとは、これらのポイントの調査を支援するものだからです。それぞれについて、どんな機能があるか、少しTipsをふりかえってみましょう。

障害の再現方法を調査するための機能

僕は、デバッガを知る前は、何度も何度も繰り返し操作し、再現する操作を調査していました。しかし、デバッガの機能を使うと、繰り返し操作しなくてもデバッグできるようにします。

例えば…
変数ビューでの値書き換えられます。
f:id:kompiro:20131013191415p:plainf:id:kompiro:20131013191414p:plain

表示ビューから例外をthrowできます。手元にソースコードがないクラスの中で例外が起きていて、その再現が難しい場合とかに重宝します。
f:id:kompiro:20131013200812p:plain

Drop to Framef:id:kompiro:20131013191339p:plainを使うと実行状態を破棄できるので、何度でも繰り返し実行できます。(※インスタンス変数や
ローカル変数は元に戻りますが、クラス変数など元に戻らないものもあることに注意)
f:id:kompiro:20131013191340p:plain

ソフトウェアの内部状態を調査するための機能

僕がデバッガを知らない頃は、標準出力に変数の値を出力して内部状態を確認してました。
標準出力に変数を出力するなど、デバッグソースコードを書き換えてしまうと、そのままコミットしてしまう事もあるでしょう。
デバッグ情報がコンソールに出力され、意図せずに内部状態が見えてしまう状態は良いものではないですよね。
しかし、デバッガを使えば、ソースコードを書き換えずに内部状態を調査できます。

例えば…
変数ビューでは変数の値が見られます。
f:id:kompiro:20131013191422p:plain

式ビューでは、各オブジェクトのメソッドの実行結果を監視できます。
f:id:kompiro:20131013191343p:plain

コードの上の変数も、実行中であれば選択してInspectできます。
f:id:kompiro:20131013191424p:plain

表示ビューではデバッグ中のプロセスに対し、コードを書くことで状態をInspectできます。
f:id:kompiro:20131013203119p:plain

こうしてみると、デバッガの機能の多くは、これらの2つのポイントを調査するためのものだとわかると思います。

得られている情報による障害の分類と、効率的なデバッグ

さて、報告された障害は、得られている情報によりいくつかの状態に分類できます。

  • 再現手順が報告されていて、その通りに実施すると障害を再現できる
  • 再現手順が報告されているが、その通りに実施しても障害を再現できない
  • 再現手順は不明だが、スタックトレースなどで例外が発生する箇所がわかっている
  • 再現手順は不明だが、再現しそうなコードの箇所を推測できている
  • 再現手順は不明の上、どこでその処理が行われているかよく分からない
  • 障害が報告されているが、再現手順が不明で報告されており、調査してみたが原因が推測できない

それぞれどうやってデバッグするでしょうか。順に見て行きましょう。

再現手順が報告されていて、その通りに実施すると障害を再現できる場合

障害の再現手順が明確にわかっていて、その通りに実施したら障害を再現できる場合、障害が起きているコードの場所がわかればかなりの確率で解決できるでしょう。最近は、障害報告に動画が使われる事もありますが、動画で報告されている場合は、この分類に入るでしょう。
この場合、予めログなどを埋め込み、どの処理が呼び出されているかわかるようにしておけば、呼び出される処理の場所にブレークポイントを貼り付けられるので、障害が発生する箇所の特定にそれほど時間をかけずに済むでしょう。

再現手順が報告されていて、その通りに実施しても障害を再現できない場合

再現手順の通りに実施しても障害を再現できない場合は、

  • 実行環境が違う(OSやブラウザ、バージョン、環境変数など)
  • 再現手順を実施する前処理が足りていない
  • 再現手順が漏れている

など、障害を再現できない要因がいくつか考えられます。例えば、

  • 報告者と実行環境が異なる事が明確な場合、実行環境をできるだけ揃えてみる
  • 前処理として書かれている項目の手順が不明瞭な場合、前処理が足りていない事を疑う
  • 再現手順を実施してみると、実際は操作できない手順があるなど、手順が怪しい場合は手順の漏れを疑う

等々が考えられます。それでも再現できない場合は、現象と再現手順が報告されていることを意識しながら、再現手順を実施した時に呼び出されるソースコードを読んでみましょう。障害の原因を推測できる場合があります。障害の原因が推測できれば、その周辺にブレークポイントを貼り、デバッガを開始します。障害が起きそうな内部状態にならないか、観察しましょう。特に実行環境が異なる場合など、デバッグしづらい場合は、内部状態を書き換えて再現しないか、試してみてください。

再現手順は不明だが、スタックトレースなどで例外が発生する箇所がわかっている

再現手順が不明の場合、本当にその障害があるのか、確認できていません。そういう意味では、修正しないでも良さそうなものです。しかし、スタックトレースなどで例外が発生している事が判明している場合、それは障害が発生した証拠が残っている事なので、速やかに調査を始め、修正を試みましょう。

例外が発生する箇所がわかっている場合、例外が発生した箇所周辺に原因があることを特定できているといえます。そのため、その周辺コードを読み、どういう状態で例外が起こるか推測し、例外が発生する箇所の周辺にブレークポイントを貼るか、例外自体にブレークポイントを貼り、その処理が呼び出される操作を行い、デバッガを使って例外が再現する内部状態をシミュレートしてみます。意図した通り例外が発生するのであれば、そういう内部状態にならないようにガードするなど、コードを修正しましょう。

再現手順は不明だが、再現しそうなコードの箇所を推測できている

再現手順は不明でも、障害の内容から、再現しそうなコードの箇所が推測できている場合があります。この場合も調査してみるべきでしょう。
障害の内容から再現しそうなコードの箇所が推測できてるのであれば、デバッガを使って障害が発生する内部状態を再現してみます。そして、内部状態を再現して障害が発生するのであれば、その内部状態が発生しそうな手順を推測し、再現するか試します。障害が再現するのであれば、その内部状態にならないように修正しましょう。

再現手順が不明の上、どこでその処理が行われているかよく分からない

障害の内容を見て、どこでその処理が行われているか推測できない場合は、その処理が行われている場所がどの辺りなのか、チームの他のメンバーに聞いてみましょう。チームに話してみて、推測できない場合は、再現不能として障害の修正を諦めるべきかもしれません。

障害が報告されているが、再現手順が不明で報告されており、調査してみたが原因が推測できない

障害を調査してみても原因が推測できない場合、チームの他のメンバーに調査を引き継いでもらいましょう。他のメンバーが引き継げるよう、チケットなど障害を管理しているシステムに、調査した内容を追記し、調査を代わってもらいます。
調査した内容をまとめてみると、コードと障害の理解が深まり、原因に気づく事もあります。他のメンバーに引き継いでもらった場合も、自分では気づかなかったことに気づくかもしれません。

忘れてはならない大事な事

障害を修正すると、デグレードを起こすリスクがあることを忘れないでください。障害の影響が軽微なのに修正による影響が広範囲に及びそうな場合は、チームに相談しましょう。場合によっては障害を修正しない、という判断になるかもしれません。
また、修正内容が心配な場合、チームメンバーにレビューを依頼する勇気を持ちましょう。

最後に、そもそも論でデバッガを使ったデバッグを考えてみると・・・

ただ、そもそも論で考えると、デバッガを使ったデバッグは、案外負けパターンなのかもしれません。
と言うのも、最初に書きましたがデバッガでできることは、内部状態の調査と障害の再現の2つです。これらはxUnit等のテスティングフレームワークとテストコードを用意する事で、素早く実施することができます。
テスティングフレームワークを使って十分に検証されていれば、どういう操作が行われた時に状態がどうなるのか、テストコードから内部状態を推測できます。また、デバッガを使ったデバッグよりもテスティングフレームワークを使った方が、短い時間で再現を確認できるでしょう。そして、テストコードで検証しているレベルでデグレードの発生が確認できるのもメリットです。

テスティングフレームワークとテストコードはとても便利なツールです。しかし、テストコードをただ書くだけでは、時間を費やす事が多く、効果を上げられません。テストコードを書くにも、スキルが入ります。このスキルは、上達させる事ができるものです。効果的にテストコードを書くにはどうすればよいでしょうか?それはまた、別の機会に。