アサーションまたは例外を使用して契約で設計しますか?[閉まっている]


123

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

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

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

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


3
契約による設計の背後にある全体的なポイントは、実行時に前提条件を検証する必要がない(そしておそらく間違いないはずです)ことです。前提条件を使用してメソッドに渡す前に入力を確認します。これにより、契約の終了を尊重します。入力が無効であるか、契約の終了に違反している場合、プログラムは通常、通常の一連のアクション(必要な操作)で失敗します。
void.pointer

いい質問ですが、実際に受け入れられた回答を(投票が示すように)切り替える必要があると思います!
DaveFar 2012

いつまでも私は知っていますが、この質問には実際にはc ++タグが必要ですか?別の言語(Delpih)で使用するためにこの答えを探していましたが、同じルールに従わない例外やアサーションを備えた言語は想像できません。(それでもスタックオーバーフローのガイドラインを学習しています。)
Eric G

この応答では、非常に簡潔な応答が返されます。「アサーションがその正確性に対処する一方で、例外はアプリケーションの堅牢性に対処します。」
Shmuel Levine 2015年

回答:


39

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

したがって、例外を使用してそれらをうまく使用してください。優れた強固な例外階層を使用し、キャッチしてデバッガでスローする例外をフックしてキャッチできるようにします。リリースモードでは、直線的なクラッシュではなくエラーを補正できます。安全な方法です。


4
アサーションは、正確性のチェックが適切に実装するのに非効率的または非効率的である場合に、少なくとも有用です。
Casebash 2009年

89
アサーションの要点は、エラーを修正することではなく、プログラマーに警告することです。そのため、リリースビルドでそれらを有効にしておくことは役に立ちません。アサートの起動によって何が得られますか?開発者はジャンプしてデバッグすることができません。アサーションはデバッグ補助であり、例外の代わりにはなりません(例外もアサーションの代わりにはなりません)。例外は、エラー状態をプログラムに警告します。アサートは開発者に警告します。
2009年

12
ただし、内部データが修正後に破損している場合は、アサーションを使用する必要があります。アサーションがトリガーされた場合、何かが/ wrong /であることを意味するため、プログラムの状態を推測できません。アサーションが発生した場合、データが有効であるとは想定できません。これが、リリースビルドがアサートする必要がある理由です。問題がどこにあるかをプログラマに伝えるのではなく、プログラムがシャットダウンして大きな問題のリスクを冒さないようにするためです。プログラムは、データが信頼できる場合、後で回復を容易にするためにできることを実行するだけです。
coppro 2009年

5
@jalf、リリースビルドではデバッガーにフックを設定できませんが、ロギングを活用して、開発者がアサーションの失敗に関連する情報を確認できるようにすることができます。このドキュメント(martinfowler.com/ieeeSoftware/failFast.pdf)では、ジムショアは次のように指摘しています。見つけるのが最も難しく、問題を説明する適切に配置されたアサーションは、あなたの労力を節約することができます。」
StriplingWarrior 2010年

5
個人的には、契約アプローチによる設計を主張することを好みます。例外は防御的であり、関数内で引数のチェックを行っています。また、dbcの前提条件では、「動作範囲外の値を使用すると動作しません」とは表示されませんが、「正しい答えを提供することは保証できません。アサートは、条件違反のある関数を呼び出しているというフィードバックを開発者に提供しますが、よりよく知っていると感じた場合に、その使用を止めないでください。違反により例外が発生する可能性がありますが、私はそれを別のことと考えています。
Matt_JD

194

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


異なるモジュール/アプリケーションに座ってシリアライズ/デシリアライズして、最終的に同期を外すとどうなりますか?読者の側では、物事を間違った方法で読み取ろうとするとアサートを使用する傾向があるため、常に私の間違いですが、一方で、外部データがあり、最終的には予告なしにフォーマットを変更する可能性があります。
スラバ

データが外部の場合は、例外を使用する必要があります。この特定のケースでは、プログラムを死なせるだけでなく、それらの例外もキャッチして、適切な方法で処理する必要があります。また、私の答えは経験則であり、自然の法則ではありません。:)したがって、各ケースを個別に検討する必要があります。
ディマ2014年

関数f(int * x)にx-> lenという行が含まれている場合、vがnullであることが証明されているf(v)がクラッシュすることが保証されています。さらに、vの初期の段階でnullであることが証明されていても、f(v)が呼び出されることが証明されている場合は、論理的に矛盾しています。これは、bが最終的に0であることが証明されているa / bと同じです。理想的には、そのようなコードはコンパイルに失敗するはずです。問題がチェックのコストでない限り、仮定チェックをオフにすることは完全にばかげています。仮定が違反された場所を覆い隠すためです。少なくともログに記録する必要があります。とにかく、クラッシュ時の再起動設計が必要です。
Rob

22

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

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

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

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


6

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

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

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


5

「アサートがデバッグモードでのみ失敗する」というのは正確には当てはまりません。

では、ソフトウェアの構築、第2版オブジェクト指向バートランド・メイヤー、リリースモードでの前提条件をチェックするためのオープン著者の葉ドアによります。その場合、アサーションが失敗するとどうなりますか...アサーション違反例外が発生します!この場合、状況からの回復はありません。ただし、何か有用なことを行うことができます。エラーレポートを自動的に生成し、場合によってはアプリケーションを再起動します。

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

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

どちらの場合も、テスト中にそのバグを検出するのが理想的であり、アサーションを有効にしてテストの重要な部分を実行する必要があります。ここで説明しているのは、テストが完了していないために事前に検出されなかったシナリオで、実稼働コードで前提条件が失敗するというまれなケースに最適なポリシーです。

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

参照:アサーションはいつプロダクションコードに残すべきですか?


1
あなたの主張は間違いなく有効です。SOは特定の言語を指定しませんでした-C#の場合、標準のアサートはSystem.Diagnostics.Debug.Assertです。これデバッグビルドでのみ失敗し、リリースビルドでのコンパイル時に削除されます。
ヨーヨー

2

巨大でした comp.lang.c ++。moderatedのリリースビルドでのアサーションの有効化/無効化に関するスレッド。数週間あれば、これについての意見の変化を見ることができます。:)

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

  1. ある種のOSタイプの障害で死に、打ち切りの呼び出しが発生します。(アサートなし)
  2. アボートする直接呼び出しを介して死にます。(アサートあり)

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

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

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


2

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

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

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


1

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

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

例外は「ちょっとユーザー、何かがうまくいかなかった」通知です(明らかに、ユーザーに通知されないようにコードで通知をキャッチできます)が、これらはJoeユーザーがアプリを使用している実行時に発生するように設計されています。

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

前提条件の多くがユーザー指定のデータであることを忘れないでください。そのため、ユーザーにデータが良くないことをユーザーに知らせるための良い方法が必要になります。これを行うには、多くの場合、エラーデータを呼び出しスタックからやり取りしているビットに返す必要があります。その場合、アサートは役に立ちません-アプリがn層の場合は二重になります。

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


0

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


0

上記の答えは正しいです。パブリックAPI関数には例外を使用してください。このルールを曲げたいと思う唯一の場合は、チェックの計算コストが高いときです。その場合は、アサートに含めることができます。

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


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;
}

OPの質問でC ++に関連するものを見たことはないと思います。それはあなたの答えに含めるべきではないと私は信じています。
ForceMagic 2013

@ForceMagic:私がこの回答を投稿した2008年の質問にはC ++タグがありました。実際、C ++タグは5時間前に削除されました。いずれにしても、このコードは言語に依存しない概念を示しています。
2013

0

私は、他のいくつかの答えを自分の考えでここにまとめてみました。

プロダクションで無効にしたい場合は、アサーションを使用してください。開発ではなく、プロダクションで無効にする本当の唯一の理由は、プログラムを高速化することです。ほとんどの場合、このスピードアップはそれほど重要ではありませんが、コードがタイムクリティカルであったり、テストの計算コストが高い場合があります。コードがミッションクリティカルである場合、スローダウンにもかかわらず例外が最も良い場合があります。

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

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


0

経験則として、私は、assert式を使用して内部エラーと外部エラーの例外を見つけます。ここからグレッグによる以下の議論から多くの利益を得ることができます

アサート式は、プログラミングエラーを見つけるために使用されます。プログラムのロジック自体のエラーまたは対応する実装のエラーのいずれかです。アサート条件は、プログラムが定義された状態のままであることを確認します。「定義された状態」とは、基本的にプログラムの想定に一致するものです。プログラムの「定義された状態」は、「理想的な状態」または「通常の状態」である必要はなく、「有用な状態」である必要もないことに注意してください。

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

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

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

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

PS:同様の質問をチェックすることをお勧めします:Exception Vs Assertion


-1

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

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

入力値を「修正」する際の問題は、呼び出し側が期待どおりの結果を得られないことです。これにより、プログラムの完全に異なる部分で問題が発生したり、クラッシュしたりして、デバッグが困難になります。

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

assert(value>0);
if(value<=0) throw new ArgumentOutOfRangeException("value");
//do stuff
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.