Edited at

デバッグ手法まとめ


この記事のライセンス



この記事はCC BY 4.0(クリエイティブ・コモンズ 表示 4.0 国際 ライセンス)の元で公開します。


デバッグは大事な能力

設計やテストも大事ですが、デバッグも大事です。

でも、実装を進めていくには、自分の書いたコードがどのように実行されるか?

テストが落ちた、意図通り実行されなかったとき、それを確かめて自分で書いたロジックを訂正する力が必要です。

良く訓練されたプログラマはデバッグの腕も良く訓練されています。

debugができないとプログラミングを覚えられないでしょう。

そこで、自分の普段行っている方法をまとめました。

プログラミング初心者の方には参考になるかもしれませんが、上級者の方々には他の技法も教えて欲しいです。


  • ちなみに以下の見出しについているデバッグ技法の名前は正式な名前ではなく、俗称や私が現場で呼んでいる呼称を使用したものです。


print(printf) debug

問題が起こりそうな場所で、print文を挿入し、変数の状態を確認します。

可能な限りロジックを含まないほうが良いが、人間に読めないもの(例えば暗号文など)の場合は最低限のロジックと一緒に実行します。

def price(product)

p product.sale? # print debug用
if product.sale?
return product.price - 10
end
product.price
end


メリット


  • 誰でもできる簡単な方法です


デメリット


  • デバッグ文を消し忘れることがあるかもしれません


logger debug

print debugの代わりにログを吐くようにします。

様々な理由で手元の環境では特定しにくいようなbugの場合に、別の環境にコードをデプロイして行うこともできます。

またdebugが必要なほど複雑なロジックを含むコードには、そもそもlogを仕込んでおく方が、後から問題が発生したときに運用がしやすいということもあるので、積極的に使っていくのが良さそうです。

logger("APIを呼び出します。")

res = call_api
logger("APIの返り値: #{res}")


メリット


  • 表示される場所は無害なので、前述のように本番環境や別環境でも実行が可能です。

  • デバッグ文を消し忘れても変なことにはならない


step debug

ブレイクポイントを指定し、その場所でプログラムを自分の手で実行できるものです。

IDEに付属のものやJavaScriptだとブラウザ上、rubyやperlなどの言語ではコマンドライン上で、ステップデバッグを行うためのライブラリがあります。

printだけでなく、様々な関数の実行なども行なえます。

stepout/stepinなどの操作を覚えることで、ブレイクポイントした行から、

行を進めていくこともできます。実行リアルタイムでdebugをすることができます。

デバッガによっては、その行で定義されている変数を表示してくるものもあるため、

print文を実行する必要すらない場合もあります。

print debugのようにプログラムに変更を加える必要がないのも大きな魅力です。

誤ってdebug文がリリースされたということも少なくなるでしょう。

(スクリプト言語などの場合はstepデバッグのブレイクポイントをプログラム中に書き込む必要があるものも多い。できれば外から操作できるデバッグツールを使いたいところ)

ただ、一部副作用が生じるような関数呼び出しを行ってしまったりすると、

プログラムの動作自体が変わってしまうので、注意は必要。

(例えば、ステップデバッグ中にDBへの挿入を行う関数を実行したら、再度プログラムによってDBへの挿入を行ってしまう。同じ事がオブジェクトにも言えます)


メリット


  • IDEを利用した場合、コードに一切触る必要がない

  • 豊富な機能が使える


デメリット


  • メリットに対してデメリットは小さいですが強いて挙げるなら


    • デバッガにバグがあるとさらにハマる

    • デバッガに頼りすぎた実装をし、テストを疎かになることがある

    • 副作用のあるメソッドを実行すると、実際の実行パターンと異なる結果が得られる

    • 無意識に変数代入などコードを動かしてしまい実際の実行パターンと異なることがある(rubyなどの場合)




return debug (二分探索)

他のデバッグ手法と組み合わせて行います。

プログラムの実行を行い途中で終了(return)させてしまいます。

それを3段階で行う事でどこにbugが潜んでいるかを挟み撃ちにして追い込んで行く手法です。

1, プログラムの実行命令の最初でprint debug

2, 実行命令の最後でprint debug

3, 中間地点でprint debug

この3つを繰り返し行います。

1で意図した結果が出ない場合は1より前に問題がある。

3で問題が出ない場合はそれより後に問題がある。

1で発生しない2で発生しない3で問題が発生しない場合は、2と3の間でもう一度この作業を繰り返す。

1で発生しない2で発生3で発生の場合は、もちろん1と3の間でこの作業を繰り返す。

というわけです。

例えば下記のようなプログラムがあり、mainが実行される場合、

最初にbeforeの前とafterの後にdebug文を挿入します。

そもそも最初のbeforeの前のprintが呼ばれなかった場合、mainの呼び出しに問題があります。mainのdebugを終了します。

afterあとのprint文が実行できなければ、before process_1 process_2 afterのいずれかに問題がありますので、ちょうど実行の半分のprocess_1とprocess2の間でプログラムを終了するようにし、debugを行います。

この繰り返しを行うということですね。はい。

def main

before
process_1
process_2
after
end

private

def before
end

def after
end

def process_1
end

def process_2
end

これができれば、実力不足や能力不足でdebug(バグ取り)できなくとも、bugを発見できないことはないですね。


  • 最初から順番にdebugしていく

  • 経験と勘に裏打ちされたdebug

なども有効ですが、return debugは機械的に行なえるから良さそう。

疑問 そもそもreturn debugなんて言葉ないのでしょうか??


参考文献

昔の論争を蒸し返すようであれですが。。。

2009-03-22 - 未来のいつか/hyoshiokの日記

わたしがprintf()デバッグをする理由 - 本当は怖い情報科学

return debugという名前でいいのか自信がないのですが。。

デバッグ - Wikipedia