プログラミング一般で使えるデバッグ方法


概要

プログラミング言語に依存しない、一般的なデバッグ技法をまとめる。

障害箇所の絞り込み

二分探索

前提

この方法を使うためには、処理が逐次実行されていることが前提となる。

やり方

  1. まず、普通の二分探索同様、バグが眠っていそうな範囲を大まかに半分程度に分ける。
  2. 前半の処理を実行し、正常に実行されることを確認する。
  3. 前半の処理が正常に実行されれば、バグが後半の処理のどこかにあることが分かる。
  4. もし、前半の処理が失敗すれば、バグは前半の処理のどこかにあることになる。

上記の手順を、障害箇所が特定できるまで繰り返す。これが二分探索によるデバッグである。

適用範囲

プログラミング

商用システムのプログラミングでは、処理が逐次的に実行されることが多く、この手法は大半の障害の切り分けに有用である。

ネットワーク

ネットワークの障害においても、障害箇所の切り分けで使えることが多い。デフォルトゲートウェイまで通信できるか、インターネットには出られるかなど、通信の正常を確認したり、Webサーバーに届いているか、アプリケーションサーバーまで届いているかなど、HTTP通信の切り分けに使えたりもする。

性能問題

これはプログラミングに含まれるが、いわゆる機能の部分でのバグではなく、性能が出ないケースでもこの手法は有効である。
処理の中のどの部分で性能がぐっと落ちるのか、二分探索で調べることができる。
SQLなどでも、この手法で切り分けていくと、特定の処理で急に重くなっていることが発見できることがある。

コンパイルエラーと実行時エラー

コンパイルでこけたら、文法エラーであり、実行時にこけたら実行時エラーである。
SQLなど、実行するタイミングでコンパイルされる言語でも、先に文法チェックを走らせられる場合があり、これでどちらのエラーか切り分けられる。

やり方の工夫

ミニプログラムで動作を確認する

疑わしい箇所がシステムの奥に埋め込まれていて、調査範囲が絞り込みづらい場合、うまく動かない箇所を切り出して別に実行してみるのが有効である。
埋め込みSQLのデバッグは、そのSQLをコピーしてSQL単体で実行した方がデバッグしやすいだろうし、プログラムの文法があっているか自信がなければ、小さなサンプルプログラムを作って実行してみれば良い。最近は大概○○Fiddleという感じでオンライン上で実行できる環境があったりする。そこでチェックすれば簡単である。

printfデバッグ

いわゆるステップ実行というやつである。IDEで実行中の変数の中身を見れたりすれば、わりとすぐに原因が分かることが多い。そこまで開発環境が整っていなくても、変数の中身を出力したり、ステップごとにログを出力したりできないケースは少ない。何か方法があるはずである。

正常に動作している別のアプリの設定と比較する

Webサーバーやアプリケーションサーバーの設定に問題がある場合、どうやってデバッグしたら良いか。
前述の二分探索以外に、正常に動いているアプリの設定と比較するというやり方がある。
Webサーバーが3台あって、1台だけうまく動かない場合、設定画面を開いて、ひとつひとつ正常に動作しているWebサーバーと比較する。
すると、うまく動かないWebサーバーだけ設定が違っていることがある。
ただ、Webサーバーの設定項目というのはたくさんあるのであって、ある程度疑わしい範囲を絞り込むことはやはり別途必要になる。
しかし、正常に動いているアプリが見つかれば、不具合が起きているアプリを正常に動かせる見通しはそれなりに明るくなるわけなので、この方法も大切である。

障害の発見

障害に関する記録を調べる

エラーメッセージ

エラーメッセージがそのまんま教えてくれていることがある。これは重大なヒントであって、無視すべきではない。もちろん、必ず正しいとは限らない。

エラーログ

エラーログがどこかに吐かれていないか見る。アプリケーションログではなく、Windowsのイベントログに何かあるかもしれない。Linux系は知らないが、話は一緒のはずである。エラーログが出ているはずなら、見てみる価値はある。

ただのログ

アクセスログとか。ともかく記録が見つかれば、それが手がかりになる。

大量のデータを流す

大量のデータを流してみる目的はいくつかある。

障害を再現する

事象を再現できれば、なんとかなるものである。そもそも障害が再現できないとき、大量にデータを流してみて、異常を発生させることを狙うわけである。

障害が発生するデータの特徴をつかむ。

1万件のデータを流して、30件程度エラーになったとする。
そのデータには何か特徴がないだろうか? 例えば、「特定のクラスタの会員データがエラーになっている」ということはないか、調べてみるのである。

障害が発生していないことの確認

修正前後のソースコードでそれぞれデータを流し、処理結果が一致している場合、流したデータについて行われた処理の結果が一致していることになる。このとき、何万件という大量のデータを流せば、ほぼほぼ機能の正常動作が確認できる。リファクタリングをしたときや、修正箇所の周囲でデグレードが起きていないことを確認したいときにこの方法が使える。

直近の修正箇所を疑う

1箇所しか修正していないなら、そこがバグっている可能性が高い。
リリースが何回か行われているなら、どのリリースまでは正常に動作しているか、例の「二分探索」で調べれば良いのである。

データを疑う

流れてくるデータが間違っている可能性もある。例えば、「会員名」のデータに、予想外に長い名前のデータが入っていて、システムが落ちているのかもしれない。もちろん、そういうデータを許容したUIが悪いという結論になるかもしれないが、データの異常を疑うことも、障害の特定には必要なことである。

平行プログラミングのデバッグ

これは難しいし、そもそも並行処理を書いた経験が少ないのだが、知っている、または聞いた範囲で書いておく。

処理の実行速度をわざと遅らせる

処理の合い間合い間にsleep処理を仕込んで、ひとつひとつの処理をゆっくり流すことで、race conditionなどのエラーを再現するという手法を読んだことがある。これは使えることがあると思うので、ここに記しておく。

スレッドの数を大幅に増やす

5,6個のスレッドが平行して動く処理があったとする。このスレッドの数を2~3000スレッドに増やす。こうすることで、障害を発生しやすくし、障害の発見に役立てる。

Webサイトへのアクセス数を増やす。

Webサイトへの大量アクセス時に障害が起きる場合、Fiddlerなどのデバッグ用プロキシサーバーを立てて、HTTPアクセスを自動で行うようにプログラミングする。こうすると、数人のチームであっても、大量のHTTPアクセスを実現でき、障害の再現を試みることができる。

参考文献

  • 『CODE COMPLETE 第2版 上 完全なプログラミングを目指して』スティーブ マコネル (著)
  • 『Java言語で学ぶデザインパターン入門 マルチスレッド編』結城 浩 (著)