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