利用できるデバッガがないとしたら、(期待どおりに)機能しないコードをデバッグするための効果的なアプローチは何でしょうか?
利用できるデバッガがないとしたら、(期待どおりに)機能しないコードをデバッグするための効果的なアプローチは何でしょうか?
回答:
いくつかのテクニックがあります:
ロギング。ファイルにアクセスできない場合は、シリアルコンソールまたは使用可能な出力デバイスにログオンします。常にロギングを念頭に置いてコードを記述し、「なし」から「過大」までのさまざまなロギングレベルの条件付きコンパイルを許可することをお勧めします。
それを削減します。バグの疑いのあるポイントの周囲のコードの部分を1つずつ除外し、バグがなくなったときに行ったことを分析します。
アサーションまたは契約。コードを最初からアサートで埋めることは良い考えです。デバッグに役立つだけでなく、コードの追加ドキュメントとしても機能します。
2.に似ています。バグが消えたり動作が変わったりしない限り、入力を変更してコードを再シャッフルします。ポインター関連のバグはその振る舞いが非常に不安定になる傾向があるため、(CまたはC ++でコーディングしている場合は)さまざまな最適化レベルを試してみるとよい場合がよくあります。
3.の自動化バージョン-インストルメント済みコードを使用します。たとえば、valgrindでバイナリを実行します。
そしてもちろん、実行環境やコードの性質に応じて、さらに多くのツールやトリックがあります。
問題のあるコードを1つずつ確認しながら、同僚に連絡して問題の詳細を説明します。
多くの場合、説明するという行為は、あなたの同僚またはあなた自身のいずれかに明らかにします。
プログラムの出力を管理するロギングシステムはありますか?少なくとも、印刷するコンソールまたは書き込み可能なファイルはありますか?コンソールまたはログファイルを使用すると、デバッガーなしでデバッグできます。出力がどうあるべきかがわかるようなプログラムを入力し、出力が機能することを確認し、ログにプロセスの詳細がたくさんあることを確認します。次に、間違った出力を与える入力で試してください。うまくいけば、ログで問題の詳細なトレースが得られます。
依存します。以前は機能しましたか?かつて機能していたコードが突然機能しなくなった場合は、最新の変更を慎重に調べる必要があります。
1)バグを100%再現可能にするため、またはできる限り100%に近づけるために必要なことをすべて実行します。
2)printf()またはその他のロギング機能を使用して、問題を追跡します。ただし、これは芸術であり、バグの性質によって異なります。
バグの場所がまったくわからないが、たとえば、ある時点で条件が偽になる(プログラムの状態が壊れた-isBroken()と呼ぶ)ことがわかっている場合は、ドリルダウン/パーティション検索を実行できます問題の場所を把握する。たとえば、主要なメソッドの最初と最後にisBroken()の値を記録します。
void doSomething (void)
{
printf("START doSomething() : %d\n", isBroken());
doFoo();
doBar();
printf("END doSomething() : %d\n", isBroken());
}
ログに表示される場合
START doSomething() : 0
END doSomething() : 1
何か問題があったことを知っています。したがって、他のすべてのロギングコードを削除して、この新しいバージョンを試してください。
void doSomething (void)
{
printf("START doSomething() : %d\n", isBroken());
doFoo();
printf("AFTER doFoo() : %d\n", isBroken());
doBar();
printf("END doSomething() : %d\n", isBroken());
}
ログでこれを見るかもしれません
START doSomething() : 0
AFTER doFoo() : 0
END doSomething() : 1
これで、doBar()がバグをトリガーすることがわかったので、doBar()で上記の手順を繰り返すことができます。理想的には、エラーを1行に絞り込みます。
もちろん、これは根本的な原因ではなく、バグの症状を明らかにするのに役立ちますたとえば、NULLであってはならないNULLポインタを見つけましたが、なぜですか?その後、再度ログに記録できますが、別の「壊れた」状態をチェックしています。
壊れた状態ではなくクラッシュした場合、それはほぼ同じです-ログの最後の行は、どこが壊れているかについてのヒントを提供します。
他の答えが失敗した後、常にバイナリ検索デバッグがあります:
注:この手法は、問題を確実に再現できる場合にのみ機能します。
「「最も効果的なデバッグツールは、慎重に検討されたものであり、慎重に配置された印刷ステートメントと相まって」-ブライアンカーニハン '今日は本当だったかもしれません!最も効果的な方法は、単体テストを確認することですが、おそらくあなたは何も持っていません。
それはバグに依存します。
バグが「コードがAを実行する理由」のようなものである場合は、「コードを実行するA」の場所を取り巻くコードの理解をテストすることは有用です。新しいバグを生成すると予想される新しいコードを導入します(このコードはBを実行する必要があり、これはCを実行する必要があります)。通常、予期しない動作を生成する新しいコードをすばやく見つけます。次に、私の心がコードの動作の更新されたメンタルモデルを構築して、最後の変更が意味をなすようになるまで辛抱強く待ちます。その後、そのメンタルモデルの変更により、通常、コードがAを実行する理由が説明されます。
2番目のケースについては、ここで詳しく説明しています。コードを継承した場合、コードがどのように機能するかについてのしっかりとしたメンタルモデルがないか、バグの特定の場所などについてよく考えていない。この場合、ドリルダウン/除算および分割印刷ステートメントを含む征服メソッドは機能します。ソース管理下にある場合は、最新のコード変更を確認してください。
「最も効果的なデバッグツールは、慎重に検討され、慎重に配置された印刷ステートメントと相まって」に拡張されます。
まず、バグが発生した瞬間を絞り込みます。ユーザーが観察できる症状をシステムで観察できるようにします。(たとえば、一部の文字列が意味不明に変化し、スクリプトの内容をポーリングし、変更時にデバッグをトリガーするループを追加します。)もちろん、エラーがクラッシュの場合は、segfault処理を追加します。
次に、問題がマルチスレッド環境にある場合は、スレッドを絞り込みます。各スレッドに識別子を付け、バグが発生したときにそれをダンプします。
スレッドを取得したら、printfsを使用して、指定したスレッドのコードを大量に散布し、スレッドが表示されるポイントを見つけます。
次に、それを作成する実際のアクションが発生する場所にバックトレースします(破壊的なアクションは、多くの場合、破損したデータが問題を引き起こす場所のかなり前になります)。破損した値が書き込まれる場所。
問題の原因となったポイントを特定したら、修正する前に、正しい動作を2度考えます。