128

契約によってプログラミングする場合、関数またはメソッドは、その責任に取り組み始める前に、その前提条件が満たされているかどうかを最初にチェックしますよね? これらのチェックを行う最も顕著な 2 つの方法は、 byassertと byexceptionです。

  1. assert はデバッグ モードでのみ失敗します。個別のコントラクトのすべての前提条件を (ユニット) テストして、実際に失敗するかどうかを確認することが重要であることを確認します。
  2. 例外は、デバッグおよびリリース モードで失敗します。これには、テスト済みのデバッグ動作がリリース動作と同じであるという利点がありますが、実行時のパフォーマンスが低下します。

どちらが好ましいと思いますか?

ここで関連する質問を参照してください

4

14 に答える 14

199

経験則では、自分のエラーをキャッチしようとする場合はアサーションを使用し、他の人のエラーをキャッチしようとする場合は例外を使用する必要があります。つまり、例外を使用して、パブリックAPI関数の前提条件を確認し、システムの外部にあるデータを取得するたびに確認する必要があります。システムの内部にある関数またはデータにはassertを使用する必要があります。

于 2008-09-22T20:06:07.050 に答える
39

リリースビルドでassertを無効にすることは、「リリースビルドで問題が発生することは決してない」と言うようなものですが、多くの場合、そうではありません。したがって、リリースビルドでassertを無効にしないでください。しかし、エラーが発生するたびにリリースビルドがクラッシュすることは望ましくありませんか?

したがって、例外を使用して、それらを適切に使用してください。優れた堅固な例外階層を使用して、確実にキャッチし、デバッガーで例外スローにフックをかけてキャッチできます。リリースモードでは、真っ直ぐなクラッシュではなく、エラーを補正できます。それは行くためのより安全な方法です。

于 2008-09-22T20:03:41.533 に答える
24

私が従う原則は次のとおりです。コーディングによって状況を現実的に回避できる場合は、アサーションを使用します。それ以外の場合は、例外を使用してください。

アサーションは、契約が順守されていることを確認するためのものです。契約は公正でなければならないため、クライアントはそれを確実に遵守できる立場になければなりません。たとえば、有効な URL とそうでない URL に関するルールは既知であり、一貫しているため、URL が有効でなければならないことをコントラクトで述べることができます。

例外は、クライアントとサーバーの両方が制御できない状況です。例外とは、何かがうまくいかず、それを回避するためにできることは何もないことを意味します。たとえば、ネットワーク接続はアプリケーションの制御外にあるため、ネットワーク エラーを回避するためにできることは何もありません。

アサーション/例外の区別は、実際にはそれについて考える最良の方法ではないことを付け加えたいと思います。あなたが本当に考えたいのは、契約とそれをどのように施行できるかということです。上記の URL の例では、Null または有効な URL である URL をカプセル化するクラスを用意するのが最善の方法です。コントラクトを強制するのは文字列から URL への変換であり、無効な場合は例外がスローされます。URL パラメーターを持つメソッドは、String パラメーターと URL を指定するアサーションを持つメソッドよりもはるかに明確です。

于 2008-09-22T20:13:47.330 に答える
7

アサートは、開発者が何か間違ったことをキャッチするためのものです (自分自身だけでなく、チームの別の開発者も)。ユーザーのミスがこの状態を引き起こす可能性があることが合理的である場合、それは例外であるべきです。

同様に、結果についても考えてください。通常、アサートはアプリをシャットダウンします。状態が回復できるという現実的な期待がある場合は、おそらく例外を使用する必要があります。

一方、問題がプログラマーのエラーにのみ起因する可能性がある場合は、できるだけ早く知りたいため、アサートを使用します。例外がキャッチされて処理される可能性がありますが、それについては決してわかりません。はい、リリース コードでアサートを無効にする必要があります。わずかな可能性がある場合にアプリを回復させたいからです。プログラムの状態がひどく壊れている場合でも、ユーザーは自分の作業を保存できる可能性があります。

于 2008-09-22T21:39:29.683 に答える
5

「アサートはデバッグ モードでのみ失敗する」というのは厳密には正しくありません。

Bertrand MeyerによるObject Oriented Software Construction, 2nd Editionでは、著者はリリース モードで前提条件をチェックするためのドアを開けたままにしています。その場合、アサーションが失敗したときに何が起こるかというと、アサーション違反の例外が発生します! この場合、状況から回復することはできません。ただし、エラー レポートを自動的に生成し、場合によってはアプリケーションを再起動することで、何か役に立つことができます。

この背後にある動機は、前提条件は通常、不変条件や事後条件よりもテストに費用がかからず、場合によってはリリース ビルドの正確性と「安全性」が速度よりも重要であるということです。つまり、多くのアプリケーションでは速度は問題ではありませんが、堅牢性(プログラムの動作が正しくない場合、つまりコントラクトが破られた場合にプログラムが安全に動作する能力) は重要です。

前提条件チェックを常に有効にしておく必要がありますか? 場合によります。それはあなた次第です。普遍的な答えはありません。銀行向けのソフトウェアを作成している場合は、1,000 ドルではなく 1,000,000 ドルを送金するよりも、警告メッセージで実行を中断する方がよい場合があります。しかし、ゲームをプログラミングしている場合はどうでしょうか? 取得できるすべての速度が必要な場合があります。前提条件がキャッチされなかったバグが原因で (有効になっていないため)、誰かが 10 ポイントではなく 1000 ポイントを取得した場合は、運が悪いでしょう。

どちらの場合も、理想的にはテスト中にそのバグをキャッチする必要があり、アサーションを有効にしてテストの大部分を実行する必要があります。ここで議論されているのは、不完全なテストのために以前に検出されなかったシナリオで、実稼働コードで前提条件が失敗するまれなケースに対する最良のポリシーは何かです。

要約すると、少なくとも Eiffel では、アサーションを有効のままにしておくと、アサーションを使用して例外を自動的に取得できます。C++ で同じことを行うには、自分で入力する必要があると思います。

参照:アサーションを本番コードに残す必要があるのはいつですか?

于 2008-12-29T15:10:41.350 に答える
2

comp.lang.c ++。moderatedのリリースビルドでのアサーションの有効化/無効化に関する巨大なスレッドがありました。数週間あれば、これに関する意見がどれほど多様であるかを確認できます。:)

copproとは異なり、リリースビルドでアサーションを無効にできるかどうかわからない場合は、アサーションである必要はないと思います。アサーションは、プログラムの不変条件が破られるのを防ぐためのものです。このような場合、コードのクライアントに関する限り、次の2つの結果のうちの1つが考えられます。

  1. ある種のOSタイプの障害で死に、その結​​果、呼び出しが中止されます。(アサートなし)
  2. 中止するための直接呼び出しを介して死ぬ。(アサートあり)

ユーザーに違いはありませんが、アサーションによって、コードが失敗しない実行の大部分に存在するコードに不要なパフォーマンスコストが追加される可能性があります。

質問への答えは、実際にはAPIのクライアントが誰であるかに大きく依存します。APIを提供するライブラリを作成している場合は、APIを誤って使用したことを顧客に通知するための何らかのメカニズムが必要です。ライブラリの2つのバージョン(1つはアサーションあり、もう1つはアサーションなし)を提供しない限り、assertが適切な選択になる可能性はほとんどありません。

しかし、個人的には、この場合も例外を除いて行くかどうかはわかりません。例外は、適切な形式の回復を実行できる場合に適しています。たとえば、メモリを割り当てようとしている可能性があります。'std :: bad_alloc'例外をキャッチすると、メモリを解放して再試行できる場合があります。

于 2008-09-23T08:17:14.360 に答える
2

ここで、問題の状態に関する私の見解を概説しました:オブジェクトの内部状態をどのように検証しますか? . 一般的に、自分の主張を主張し、他者による違反を主張します。リリース ビルドでアサートを無効にするには、次のようにします。

  • コストのかかるチェック (範囲が順序付けられているかどうかのチェックなど) のアサートを無効にします。
  • 単純なチェックを有効にしておく (null ポインターやブール値のチェックなど)

もちろん、リリース ビルドでは、失敗したアサーションとキャッチされなかった例外は、デバッグ ビルド (std::abort を呼び出すだけでよい) とは別の方法で処理する必要があります。エラーのログをどこかに (おそらくファイルに) 書き込み、内部エラーが発生したことを顧客に伝えます。顧客はログ ファイルを送信できます。

于 2008-12-29T15:25:58.637 に答える
1

設計時エラーと実行時エラーの違いについて質問しています。

アサートは「プログラマー、これは壊れています」という通知であり、発生したときに気付かなかったであろうバグを思い出させるためにあります。

例外は「ユーザーの皆さん、何かがおかしい」という通知です(明らかに、ユーザーに通知されないようにコードで通知できます)が、これらは、Joeユーザーがアプリを使用している実行時に発生するように設計されています。

したがって、すべてのバグを取り除くことができると思われる場合は、例外のみを使用してください。できないと思う場合は、例外を使用してください。もちろん、デバッグアサートを使用して、例外の数を減らすことができます。

前提条件の多くはユーザー提供のデータであることに注意してください。そのため、ユーザーにデータが適切でなかったことを通知するための適切な方法が必要になります。そのためには、多くの場合、エラーデータをコールスタックから彼が操作しているビットに返す必要があります。その場合、アサートは役に立ちません。アプリがn層の場合は2倍になります。

最後に、どちらも使用しません。エラーコードは、定期的に発生すると思われるエラーに対してはるかに優れています。:)

于 2008-09-22T20:05:37.270 に答える
0

私はここで他のいくつかの答えを自分の見解で統合してみました。

本番環境でアサーションを無効にし、そのままにしておくことに誤りがある場合は、アサーションを使用します。本番環境で無効にする唯一の本当の理由は、開発ではなく、プログラムを高速化することです。ほとんどの場合、このスピードアップは重要ではありませんが、コードに時間がかかる場合や、テストに計算コストがかかる場合があります。コードがミッションクリティカルである場合は、速度が低下しても例外が最適な場合があります。

回復の可能性が実際にある場合は、アサーションが回復するように設計されていないため、例外を使用してください。たとえば、コードがプログラミングエラーから回復するように設計されていることはめったにありませんが、ネットワーク障害やファイルのロックなどの要因から回復するように設計されています。エラーは、単にプログラマーの制御の及ばないという理由で例外として扱われるべきではありません。むしろ、これらのエラーの予測可能性は、コーディングの間違いと比較して、それらを回復しやすくします。

アサーションのデバッグが簡単であるという議論を再確認してください。適切な名前の例外からのスタックトレースは、アサーションと同じくらい読みやすいです。優れたコードは特定の種類の例外のみをキャッチする必要があるため、キャッチされたために例外が見過ごされてはなりません。ただし、Javaではすべての例外をキャッチするように強制されることがあると思います。

于 2009-10-25T07:12:16.583 に答える
0

私にとっての経験則は、アサート式を使用して内部エラーと外部エラーの例外を見つけることです。ここの Greg による次の議論から多くの恩恵を受けることができます。

アサート式は、プログラミング エラー (プログラムのロジック自体のエラー、または対応する実装のエラー) を見つけるために使用されます。アサート条件は、プログラムが定義された状態のままであることを確認します。「定義された状態」とは、基本的にプログラムの想定に一致する状態です。プログラムの「定義された状態」は、「理想的な状態」や「通常の状態」、さらには「有用な状態」である必要はありませんが、その重要な点については後で詳しく説明します。

アサーションがプログラムにどのように適合するかを理解するために、ポインターを逆参照しようとしている C++ プログラム内のルーチンを考えてみましょう。逆参照の前にポインタが NULL かどうかをルーチンでテストする必要がありますか、それともポインタが NULL ではないことをアサートしてから先に進んで逆参照する必要がありますか?

ほとんどの開発者は、アサートを追加するだけでなく、アサートされた条件が失敗した場合にクラッシュしないようにポインターの NULL 値をチェックすることも望んでいると思います。表面的には、テストとチェックの両方を実行することが最も賢明な決定に見えるかもしれません

アサートされた条件とは異なり、プログラムのエラー処理 (例外) は、プログラム内のエラーではなく、プログラムがその環境から取得した入力を参照します。これらは多くの場合、ユーザーがパスワードを入力せずにアカウントにログインしようとしたなど、誰かの側の「エラー」です。また、エラーが原因でプログラムのタスクが正常に完了しない場合でも、プログラムの障害はありません。プログラムは、外部エラー (ユーザー側のエラー) により、パスワードなしでユーザーをログインできません。状況が異なり、ユーザーが正しいパスワードを入力し、プログラムがそれを認識できなかった場合。その場合、結果は同じですが、失敗はプログラムに属します。

エラー処理 (例外) の目的は 2 つあります。1 つ目は、ユーザー (または他のクライアント) に、プログラムの入力でエラーが検出されたことと、それが何を意味するかを伝えることです。2 番目の目的は、エラーが検出された後、アプリケーションを適切に定義された状態に復元することです。この場合、プログラム自体はエラーではないことに注意してください。確かに、プログラムは理想的ではない状態、あるいは何の役にも立たない状態にあるかもしれませんが、プログラミング エラーはありません。逆に、エラー回復状態は、プログラムの設計上想定されている状態であるため、プログラムで処理できる状態です。

PS: 同様の質問を確認することをお勧めします: Exception Vs Assertion

于 2014-01-05T11:25:28.123 に答える
0

私は2番目のものを好みます。テストは問題なく実行されたかもしれませんが、Murphyは予期しない問題が発生するだろうと言います。したがって、実際の誤ったメソッド呼び出しで例外を取得する代わりに、NullPointerException (または同等のもの) を 10 スタック フレーム深くトレースすることになります。

于 2008-09-22T20:08:12.180 に答える
0

両方を使用する必要があります。アサートは、開発者にとって便利です。例外は、実行時に見逃したものや予期していなかったものをキャッチします。

単純な古いアサートではなく、glib のエラー報告機能が好きになりました。これらは assert ステートメントのように動作しますが、プログラムを停止する代わりに、値を返し、プログラムを続行させます。それは驚くほどうまく機能し、おまけとして、関数が「本来あるべき」ものを返さない場合に、プログラムの残りの部分がどうなるかを見ることができます。クラッシュした場合は、どこか別の場所でエラー チェックが緩いことがわかります。

私の最後のプロジェクトでは、これらのスタイルの関数を使用して前提条件チェックを実装しました。そのうちの 1 つが失敗した場合は、スタック トレースをログ ファイルに出力し、実行を続けました。デバッグビルドを実行しているときに他の人が問題に遭遇する可能性があるデバッグ時間を大幅に節約できました。

#ifdef DEBUG
#define RETURN_IF_FAIL(expr)      do {                      \
 if (!(expr))                                           \
 {                                                      \
     fprintf(stderr,                                        \
        "file %s: line %d (%s): precondition `%s' failed.", \
        __FILE__,                                           \
        __LINE__,                                           \
        __PRETTY_FUNCTION__,                                \
        #expr);                                             \
     ::print_stack_trace(2);                                \
     return;                                                \
 };               } while(0)
#define RETURN_VAL_IF_FAIL(expr, val)  do {                         \
 if (!(expr))                                                   \
 {                                                              \
    fprintf(stderr,                                             \
        "file %s: line %d (%s): precondition `%s' failed.",     \
        __FILE__,                                               \
        __LINE__,                                               \
        __PRETTY_FUNCTION__,                                    \
        #expr);                                                 \
     ::print_stack_trace(2);                                    \
     return val;                                                \
 };               } while(0)
#else
#define RETURN_IF_FAIL(expr)
#define RETURN_VAL_IF_FAIL(expr, val)
#endif

引数のランタイム チェックが必要な場合は、次のようにします。

char *doSomething(char *ptr)
{
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL);  // same as assert(ptr != NULL), but returns NULL if it fails.
                                            // Goes away when debug off.

    if( ptr != NULL )
    {
       ...
    }

    return ptr;
}
于 2008-09-22T21:23:13.323 に答える
0

前の回答は正しいです。パブリック API 関数には例外を使用してください。この規則を曲げた方がよいのは、チェックの計算コストが高い場合だけです。その場合は、アサートに入れることができます。

その前提条件に違反する可能性が高いと思われる場合は、それを例外として保持するか、前提条件をリファクタリングして取り除いてください。

于 2008-09-22T20:51:18.957 に答える
-1

この質問も参照してください:

場合によっては、リリース用にビルドするときにアサートが無効になります。これを制御できない可能性があるため (そうしないと、アサートをオンにしてビルドできます)、このようにすることをお勧めします。

入力値を「修正」することの問題は、呼び出し元が期待どおりにならないことです。これにより、プログラムのまったく異なる部分で問題が発生したり、クラッシュしたりする可能性があり、デバッグが悪夢になります。

私は通常、ifステートメントで例外をスローして、アサートが無効になっている場合にアサートの役割を引き継ぎます

assert(value>0);
if(value<=0) throw new ArgumentOutOfRangeException("value");
//do stuff
于 2008-09-22T20:18:22.967 に答える