デバッガーが常に完璧なソリューションであるとは限らず、デバッグの頼りになるソリューションであるとは限らないことを付け加えたいと思います。デバッガーが機能しない場合がいくつかあります。
- 失敗するプログラムの部分は非常に大きく(おそらくモジュール化が不十分ですか?)、コードのステップ実行をどこから開始すればよいか正確にはわかりません。すべてをステップスルーするのは時間がかかりすぎるかもしれません。
- プログラムは多くのコールバックやその他の非線形フロー制御メソッドを使用しているため、ステップを実行するときにデバッガーが混乱します。
- プログラムはマルチスレッドです。さらに悪いことに、問題は競合状態が原因です。
- バグが含まれているコードは、バグが発生する前に何度も実行されます。これは、メインループで特に問題になる可能性があり、さらに悪いことに、問題が数値である可能性がある物理エンジンでは問題になる可能性があります。この場合、ブレークポイントを設定しても、バグが表示されずに何度もブレークポイントをヒットするだけです。
- プログラムはリアルタイムで実行する必要があります。これは、ネットワークに接続するプログラムにとって大きな問題です。ネットワークコードにブレークポイントを設定した場合、もう一方の端はステップスルーを待つことはなく、単にタイムアウトになります。システムクロックに依存するプログラム、たとえばフレームスキップを使用するゲームも、それほど良いものではありません。
- プログラムは、ファイルへの書き込みや電子メールの送信など、何らかの形の破壊的なアクションを実行しますが、実行する必要のある回数を制限したいと考えています。
- バグの原因は関数Xに到達する誤った値であることがわかりますが、これらの値がどこから来ているのかはわかりません。プログラムを何度も何度も実行しなければならないこと、ブレークポイントをどんどん後ろに設定することは、非常に面倒なことです。特に関数Xがプログラム全体の多くの場所から呼び出された場合。
これらすべての場合において、プログラムを突然停止させると、最終結果が異なる可能性があります。または、バグが発生した1行を手動で検索するのは、非常に面倒です。これは、バグが正しくない動作であるか、クラッシュであるかにかかわらず、同様に発生する可能性があります。たとえば、メモリ破損によってクラッシュが発生した場合、クラッシュが発生するまでに、メモリ破損が最初に発生した場所から離れすぎており、有用な情報が残っていません。
それで、選択肢は何ですか?
最も単純なのは、単にロギングとアサーションです。さまざまな時点でプログラムにログを追加し、得られるものと期待するものを比較します。たとえば、バグがあると思われる関数が最初から呼び出されているかどうかを確認します。メソッドの開始時の変数があなたが思っているものであるかどうかを確認してください。ブレークポイントとは異なり、特別なことは何も起こらないログ行がたくさんあっても問題ありません。後でログを検索するだけです。予想とは異なるログ行に到達したら、同じ領域にさらに追加します。バグのある領域のすべての行をログに記録できるほど小さくなるまで、さらに絞り込みます。
アサーションは、エンドユーザーに表示される効果があった場合ではなく、発生したときに誤った値をトラップするために使用できます。間違った値をすばやくキャッチするほど、それを生成したラインに近づきます。
リファクタリングと単体テスト。プログラムが大きすぎる場合は、一度に1つのクラスまたは1つの関数をテストする価値があるかもしれません。入力を与え、出力を見て、どれが期待どおりでないかを確認します。プログラム全体から単一の関数にバグを絞り込むことができると、デバッグ時間に大きな違いが生じる可能性があります。
メモリリークやメモリ踏み込みが発生した場合は、実行時にこれらを分析および検出できる適切なツールを使用してください。実際の破損が発生した場所を検出できるようにすることが最初のステップです。この後、ログを使用して、誤った値が導入された場所に戻ることができます。
デバッグは逆行するプロセスであることを忘れないでください。最終結果(バグ)があり、それに先行する原因を見つけます。それはあなたのやり方を後退させることであり、残念ながら、デバッガーは前進するだけです。これは、優れたロギングと事後分析がはるかに優れた結果をもたらすことができる場所です。