ほとんどの人は、デバッグを科学ではなく芸術として扱うようです。アートではなく科学として扱う人たちのために、新しい問題/バグ/問題に直面したときに、通常どのようなプロセスを使用していますか?
非常に一般的に言えば、私がすることは:
問題を特定してください。バグが最初に発生したときの変更点を考えてください。どこで取り組んでいますか?コードのどの部分を変更しましたか?私のバグの99%はこの方法で解決されています。それは通常ばかげたことです。
問題がどこにあるのか推測できる場合は、原因と思われるコードをよく見てください。それを読んで。声に出して読んでください。 「何を達成しようとしているのか?」と自問してください。一部の種類の問題の場合:副作用があるか、他の場所にあるコードによって、私が考えていなかった方法で影響を受ける可能性がありますか?
さまざまな方法で、どこがいつどこで問題が発生するかを分析します(以下を参照)。
それでも手掛かりがない場合は、古いバージョンのソースに同じ問題があるかどうかを確認し、開発のタイムラインで問題が最初に発生した時期を探します。これを行うには、gitなどの適切なバージョン管理システムを使用する必要があります(gitには、この種のデバッグのためにbisectと呼ばれる機能があります)。
それでも手掛かりがない場合は、休憩を取ってください。
ドローイングボードに戻ります。プログラムがどのように機能するか、またそれが実際に意味があるかどうかを確認します。
それは本当に問題の種類に依存しますが、問題がどこにあるかについての一般的な考えがあると仮定すると、次のようになります。
問題がコードの一部または最近の変更にあると思われる場合は、まずコードを単純化してバグをなくす/コメントアウト/変更するなどしてバグをなくし、問題のあるコードを元に戻して、よく見てください。
(可能な場合は)ブレークポイントを使用してデバッガーを実行し、データが正しく動作しなくなったときにデータがどのように検索しようとしているのかを調べて、どこがうまくいかないかをよりよく理解します。
テスト駆動開発を使用しようとします( [〜#〜] tdd [〜#〜] )。バグを再現するテストを作成し、テストに合格するようにしています。時々、テストを書く行為はバグを見つけるのに役立ちます。
これにより、ほとんどの場合、デバッガーから離れ、バグの再導入を防ぐための回帰テストを提供します。
いくつかのリンク:
単語科学にはいくつかの定義がありますが、「科学的方法」とより正確に呼ばれるものを参照しているように思われるかもしれません。科学的方法は、いくつかの現象(おそらくバグまたは予期しないプログラムの動作)を観察し、動作を説明するための1つまたは複数の仮説を立て、それを証明するための実験(問題を確実に再現するテストを書く)として要約できます。
発生する可能性のあるバグの種類(現象)は、実質的に無限であり、明確に定義されたプロセスを必ずしも必要としないものもあります。たとえば、コードに精通しているという理由だけで、バグを観察し、何が原因であるかがすぐにわかる場合があります。また、何らかの入力(アクション、一連のステップなど)が行われると、誤った結果(クラッシュ、出力不良など)が発生することもあります。これらの場合、多くの場合、それは多くの「科学的」思考を必要としません。いくつかの考えは検索スペースを減らすのに役立ちますが、一般的な方法は、デバッガーでコードをステップ実行して、問題が発生した場所を確認することです。
しかし、私が科学プロセスの中で最も興味深く、おそらく価値があると思う状況は、最終結果を手渡され、それがどのようにして起こったかを説明するよう求められる場合です。これらの明白な例は、クラッシュダンプです。クラッシュダンプをロードして、システムの状態を観察できます。ジョブは、その状態になった方法を説明することです。クラッシュ(またはコア)ダンプは、例外、デッドロック、内部エラー、またはユーザーが定義した「望ましくない」状態(緩慢など)を示す場合があります。これらの状況では、私は通常、次のような手順を実行します。
狭い観察:該当する場合、特定の問題を直接取り巻く調査情報。ここで明らかなのは、コールスタック、ローカル変数、それらを確認できる場合は、問題を囲むコード行です。このタイプの特定のロケーション調査は常に適用できるとは限りません。たとえば、「遅い」システムを研究する場合、このような明確な開始場所はないかもしれませんが、クラッシュや内部エラーの状況には、すぐに明らかな関心のあるポイントがある可能性があります。ここでの1つの具体的な手順は、windbgなどのツールを使用することです(読み込まれたクラッシュダンプに対して!analyze -vを実行し、それが何を示しているかを確認します)。
広い観察:システムの他の部分を調べます。システム内のすべてのスレッドの状態を調べ、グローバル情報(ユーザー数/操作/アイテム、アクティブなトランザクション/プロセス/ウィジェットなど)、システム(OS)情報などを確認します。ユーザーが外部の詳細情報を提供した場合、あなたが観察したことと関連してそれらについて考えてください。たとえば、問題が毎週火曜日の午後に発生すると彼らが言った場合、それが何を意味するのかを自問してください。
Hypothesize:これは本当に楽しい部分です(そして私はそれが楽しいことについて面白くありません)。多くの場合、逆に大量の論理的思考が必要です。システムがどのように現在の状態になったのかを考えるのはとても楽しいかもしれません。多くの人がアートだと思っている部分だと思います。そして、プログラマがランダムに物事を投げ始めて、何が付着しているかを確認しているのではないかと思います。しかし、経験上、これはかなり明確なプロセスになる可能性があります。この時点で非常に論理的に考えると、特定の状態に至る可能性のあるパスのセットを定義できることがよくあります。私たちは状態S5にいることを知っています。そのためには、S4aまたはS4bが発生する必要があり、S4aの前にS3が発生する可能性があります。多くの場合は、特定の状態につながる可能性のある複数のアイテムが存在する可能性があります。場合によっては、スクラッチパッドに単純なフロー図または状態図、または一連の時間関連の手順を書き留めておくと役立ちます。ここでの実際のプロセスは状況によって大きく異なりますが、この時点での真剣な考察(および前のステップでの再検討)は、1つ以上のもっともらしい答えを提供することがよくあります。また、このステップの非常に重要な部分は、不可能なことを排除することです。不可能を取り除くことは、解のスペースを整えるのに役立ちます(不可能を取り除いた後に残されたものについてシャーロックホームズが言ったことを思い出してください)。
実験:この段階では、前のステップで導き出された仮説に基づいて問題を再現してみます。前のステップで真剣に考えていた場合、これは非常に簡単です。時々、私は特定のテストを助けるためにコードベースを「チート」し、修正します。たとえば、私は最近、競合状態が原因であると結論付けたクラッシュを調査していました。それを確認するために、コードの数行の間にSleep(500)を置くだけで、別のスレッドが「適切な」タイミングで悪いことを実行できるようにします。これが「実際の」科学で許可されているかどうかはわかりませんが、所有しているコードでは完全に妥当です。
あなたがそれを再現することに成功した場合、チャンスはほぼ完了しているでしょう(残っているすべてはそれを修正する簡単なステップです...しかしそれは別の日のためです)。新しいテストを必ず回帰テストシステムにチェックインしてください。そして、私はそれを修正することについての前の陳述が簡単に口語になることを意図していたことを指摘しなければなりません。ソリューションを見つけて実装するには、多大な作業が必要になる場合があります。バグの修正はデバッグプロセスの一部ではなく、むしろ開発であると私は考えています。また、修正が少しでも関係している場合は、ある程度の設計とレビューが必要です。
テストケースを減らしてみてください。十分に小さい場合は、通常、問題の原因となっている対応するコードを見つけやすくなります。
新しいチェックインが問題の原因であり、以前の毎日のビルドは問題なかった可能性があります。その場合、ソース管理からの変更ログは、誰をキャッチするかを決定するのに役立ちます。
また、C/C++を使用している場合は、valgrindまたはpurifyを実行して、メモリ関連の問題を特定することを検討してください。
より実用的なアプローチ:
バグが未処理の例外に関連している場合は、スタックトレースを確認してください。 null参照、範囲外のインデックスなど、独自に定義された例外が最も一般的です。このバグをジュニア開発者に割り当てることができます。おそらく簡単で、優れた学習経験です。
すべてのマシンで発生しない場合は、おそらく競合状態またはスレッド化の問題です。これらは追跡するのがとても楽しいです、あなたの退屈な上級プログラマーをそれに置いてください。たくさんのロギング、優れた知識、優れたツールがこれを実現します。
バグのもう1つの大きなクラスは、テストチームまたはクライアントが特定の動作を好まない場合です。たとえば、ユーザーIDを表示することを決定したり、検索時にオートコンプリートが表示されないことを彼らは気に入らないでしょう。これらは本物のバグです。より広い視野でより良い製品管理と開発者を持つことを検討してください。拡張を考慮してシステムを構築する場合、開発者がこれを「修正」するのに比較的短い時間がかかるはずです。
他のすべてのバグの80%は、優れたロギングシステムを備え、それらを解決するのに十分な情報を収集することで解決されます。 Log4Net/Log4Jのような複数のレベルの複雑なロギングシステムでビルトイントレースを使用する
パフォーマンスのバグはそれ自体のカテゴリであり、ここで最も優れたルールは「最初に測定し、後で修正する!」であり、何人の開発者が問題があるかを推測し、それを修正するためだけに修正するのに驚くのは驚くでしょう。その後、応答時間はわずか3〜4%減少します。
私が午後遅くに追跡するのに苦労している厄介なバグがあるので、私の最も効果的な戦略は立ち上がって数分間離れることです。通常、考えられるエラーの原因に関する新しいアイデアは、わずか30秒後に流れ始めます。
デバッグの最も難しい部分は、問題を特定することです。特に、問題が複数の層の下に埋まっている場合はそうです。大学で音楽の録音を勉強していましたが、奇妙なことに、ここで直接適用されるスタジオエレクトロニクスクラスがありました。体系的なデバッグプロセスの例として、スタジオ環境のデバッグを使用します。
コードのデバッグはそれほど違いはありません。コードが例外をスローしていると、デバッグがはるかに簡単になります。その例外のスタックトレースから逆方向にトレースし、重要な位置にブレークポイントを設定できます。通常、変数を設定した直後、または例外をスローするメソッドを呼び出す行です。 1つ以上の値が正しくない場合があります。それが正しくない場合(あるべきでないnull、または値が範囲外の場合)、それが正しくない理由を発見するプロセスです。 IDEのブレークポイントは、電子テストポイントと同等です(メーターのプローブが回路をチェックするために設計されています)。
さて、私の本当の問題がどこにあるかを発見するという難しい部分を終えたら、将来それをチェックするためのユニットテストをいくつか作成します。
私には2つのアプローチがあります:
Divide and Conquer
パラダイムに従って各小さな部分を征服します。このアプローチはほとんどの場合私に役立ちました。