Debug.Assertと例外のスロー


92

私は、アサーションの使用方法と使用時期に関する多くの記事(およびStackOverflowに投稿された他のいくつかの同様の質問)を読みましたが、それらをよく理解しています。しかし、それでも、Debug.Assert単純な例外をスローする代わりに、どのような動機で私を駆り立てるのか理解できません。つまり、.NETでは、失敗したアサーションに対するデフォルトの応答は、「世界を停止」してユーザーにメッセージボックスを表示することです。この種の動作は変更できる可能性がありますが、それを行うのは非常に煩わしく冗長であると思いますが、代わりに適切な例外をスローすることもできます。このようにして、例外をスローする直前にアプリケーションのログにエラーを簡単に書き込むことができ、さらに、アプリケーションが必ずしもフリーズするわけではありません。

それで、もしあるとすればDebug.Assert、明白な例外の代わりに使用する必要があるのはなぜですか?アサーションを本来あるべきではない場所に配置すると、あらゆる種類の「望ましくない動作」が発生する可能性があるため、私の見解では、例外をスローする代わりにアサーションを使用しても何も得られません。私に同意しますか、それともここで何か不足していますか?

注:「理論上」の違いは何なのか(デバッグとリリース、使用パターンなど)は完全に理解していますが、それを見ると、アサートを実行するのではなく、例外をスローした方がよいでしょう。プロダクションリリースでバグが発見された場合でも、「アサーション」を失敗させたいので(結局のところ、「オーバーヘッド」は途方もなく小さいので)、代わりに例外をスローした方がよいでしょう。


編集:私がそれを見る方法、アサートが失敗した場合、それはアプリケーションが何らかの破損した予期しない状態に入ったことを意味します。では、なぜ実行を続けたいのでしょうか?アプリケーションがデバッグバージョンとリリースバージョンのどちらで実行されているかは関係ありません。同じことが両方に行きます


1
「プロダクションリリースでバグが発見された場合でも、「アサーション」が失敗することを希望します」と言っていることについては、例外を使用する必要があります
Tom Neyland

1
唯一の理由はパフォーマンスです。常にすべてをnullチェックすると速度が低下する可能性がありますが、まったく気付かない場合もあります。これは主に発生してはならないケースです。たとえば、前の関数ですでにnullチェックされていることがわかっていて、サイクルを再度チェックしても無駄がありません。debug.assertは事実上、ラストチャンスユニットテストのように機能して通知します。
2017年

回答:


175

私はあなたの推論がもっともらしいことに同意します-つまり、アサーションが予期せず違反された場合、スローすることによって実行を停止することは理にかなっています-私は個人的にアサーションの代わりに例外を使用しません。理由は次のとおりです。

他の人が言ったように、アサーションは不可能な状況を文書化する必要があります。つまり、不可能なとされる状況が発生した場合に、開発者に通知します。対照的に、例外は、例外的な状況、起こりそうもない状況、または誤った状況に制御フローメカニズムを提供しますが、不可能ではありません。私にとって、主な違いはこれです:

  • 常に、指定されたthrowステートメントを実行するテストケースを作成できるようにする必要があります。このようなテストケースを作成できない場合は、プログラム内に実行されないコードパスがあり、デッドコードとして削除する必要があります。

  • アサーションを起動させるテストケースを生成することは決して不可能であるべきです。アサーションが発生した場合、コードが間違っているか、アサーションが間違っています。いずれにしても、コードで何かを変更する必要があります。

そのため、アサーションを例外に置き換えません。アサーションを実際に起動できない場合は、アサーションを例外で置き換えることは、プログラムにテスト不可能なコードパスがあることを意味します。テストできないコードパスが嫌いです。


16
アサーションの問題は、プロダクションビルドには存在しないことです。想定された条件が満たされないということは、プログラムが未定義の動作領域に入ったことを意味します。その場合、責任のあるプログラムはできるだけ早く実行を停止する必要があります(スタックの巻き戻しも、取得する厳密さによっては、やや危険です)。はい、アサーションを実行することは通常不可能であるはずですが、実際に事態が発生したときに何が可能かわかりません。不可能だと思っていたことが本番環境で発生する可能性があります。責任あるプログラムは、違反した仮定を検出し、迅速に行動する必要があります。
kizzx2

2
@ kizzx2:OK、それであなたはプロダクションコードの行ごとにいくつの不可能な例外を書くのですか?
Eric Lippert、2010

4
@ kixxx2:これはC#であるため、Trace.Assertを使用して本番コードでアサーションを保持できます。app.configファイルを使用して、エンドユーザーに失礼ではなく、プロダクションアサーションをテキストファイルにリダイレクトすることもできます。
HTTP 410

3
@AnorZaken:あなたの主張は、例外の設計上の欠陥を示しています。他の場所で述べたように、例外は、(1)致命的な災害、(2)発生してはならない骨の折れるミス、(3)例外が非例外的な状態を示すために使用される設計の失敗、または(4)予期しない外因性の状態です。 。なぜこれらの4つのまったく異なるものがすべて例外として表されるのですか?私は私のdruthersを持っていた場合、boneheaded例外が捕捉可能ではないでしょう「ナルは、間接参照された」すべての。それは決して正しくないので、害を及ぼす前にプログラム終了する必要があります。それらはアサートのようなものでなければなりません。
エリックリッペルト

2
@Backwards_Dave:偽のアサーションは真実ではなく、悪いものです。アサーションを使用すると、運用環境では実行したくない高価な検証チェックを実行できます。また、アサーションが本番環境で違反されている場合、エンドユーザーはそれについて何をすべきですか?
Eric Lippert、2018年

17

アサーションは、プログラマーの世界に対する理解をチェックするために使用されます。アサーションは、プログラマが何か間違ったことをした場合にのみ失敗するはずです。たとえば、アサーションを使用してユーザー入力をチェックしないでください。

「起こり得ない」条件のテストをアサートします。例外は、「発生してはならないが発生する」条件です。

アサーションは、ビルド時(または実行時)にその動作を変更できるので便利です。たとえば、多くの場合、リリースビルドでは、アサートは不要なオーバーヘッドが発生するためチェックされません。これも注意すべき点です。テストが実行されない可能性もあります。

アサートの代わりに例外を使用すると、いくつかの値が失われます。

  1. 例外のテストとスローは少なくとも2行ですが、アサートは1行だけなので、コードはより冗長です。

  2. テストとスローコードは常に実行されますが、アサートはコンパイルして取り除くことができます。

  3. アサートはチェックとスローを行う製品コードとは異なる意味を持つため、他の開発者との通信が一部失われます。実際にプログラミングアサーションをテストする場合は、アサートを使用します。

詳細:http : //nedbatchelder.com/text/assert.html


「発生しない」場合は、なぜアサーションを作成するのですか。それは冗長ではありませんか?それが実際に発生する可能性があるが発生するべきではない場合、これは、例外が発生する「発生してはならないが発生する」と同じではありませんか?
David Klempfner、2018年

2
「起こりえない」は、引用符で囲まれた理由があります。それは、プログラマーがプログラムの別の部分で何か間違ったことをした場合にのみ起こります。アサートは、プログラマーのミスに対するチェックです。
Ned Batchelder、2018年

@NedBatchelder ただし、ライブラリを開発する場合、プログラマという用語は少しあいまいです。これらの「あり得ない状況」、図書館の利用者には不可能であるはずですが、図書館の作者が間違いを犯した場合には可能でしょうか。
Bruno Zell

12

編集: 投稿で行った編集/メモへの応答達成しようとしていることの種類に対してアサーションを使用するよりも、例外を使用するのが正しいようです。あなたがたがう精神的な障害は、同じ目的を果たすために例外と主張を検討しているので、どちらが「正しい」のかを理解しようとしていることだと思います。アサーションと例外を使用する方法にはいくつかの重複があるかもしれませんが、それらが同じ問題に対する異なるソリューションであることを混同しないでください-そうではありません。アサーションと例外にはそれぞれ独自の目的、長所、短所があります。

私は自分の言葉で答えをタイプするつもりでしたが、これは私が持っているよりもこの概念をより正確にしています:

C#ステーション:アサーション

assertステートメントの使用は、実行時にプログラムロジックエラーをキャッチする効果的な方法ですが、プロダクションコードから簡単に除外できます。開発が完了すると、コンパイル中にプリプロセッサシンボルNDEBUG [すべてのアサーションを無効にする]を定義するだけで、コーディングエラーに対するこれらの冗長テストの実行時コストを排除できます。ただし、アサート自体に配置されたコードは製品版では省略されることを忘れないでください。

アサーションは、次のすべてが成立する場合にのみ条件をテストするために使用するのが最適です。

* the condition should never be false if the code is correct,
* the condition is not so trivial so as to obviously be always true, and
* the condition is in some sense internal to a body of software.

ソフトウェアの通常の操作中に発生する状況を検出するためにアサーションを使用することはほとんどありません。 たとえば、通常、アサーションはユーザー入力のエラーをチェックするために使用されるべきではありません。ただし、アサーションを使用して、呼び出し元がユーザーの入力を既にチェックしたことを確認することは意味があります。

基本的に、本番アプリケーションでキャッチ/処理する必要があるものには例外を使用し、アサーションを使用して、開発には役立つが本番環境ではオフになっている論理チェックを実行します。


私はそれをすべて理解しています。しかし、重要なことは、太字としてマークした同じステートメントは例外にも適用されます。したがって、アサーションの代わりに、アサーションの代わりに例外をスローすることもできます(「発生してはならない状況」がデプロイされたバージョンで発生した場合でも、それを通知する必要があるためです[さらに、アプリケーション破損した状態になる可能性があるため、例外が適切であり、通常の実行フローを続行したくない場合があります)

1
アサーションは不変条件で使用する必要があります。たとえば、何かがnullであってはならないときに例外が使用されるべきですが、それは(メソッドへのパラメーターのように)例外になります。
Ed S.

どれだけ防御的にコーディングしたいかがすべてだと思います。
Ned Batchelder

私が同意するのは、必要だと思われることについては、例外は進むべき道です。本番で検出された障害、エラーに関する情報をログに記録する機能、実行フロー制御など。これら3つのことから、いくつかの例外を回避する必要があると思います。
トムネイランド

7

(考案された)実用的な例が違いを明らかにするのに役立つと思います:

MoreLinqのBatch拡張機能から変更

// 'public facing' method
public int DoSomething(List<string> stuff, object doohickey, int limit) {

    // validate user input and report problems externally with exceptions

    if(stuff == null) throw new ArgumentNullException("stuff");
    if(doohickey == null) throw new ArgumentNullException("doohickey");
    if(limit <= 0) throw new ArgumentOutOfRangeException("limit", limit, "Should be > 0");

    return DoSomethingImpl(stuff, doohickey, limit);
}

// 'developer only' method
private static int DoSomethingImpl(List<string> stuff, object doohickey, int limit) {

    // validate input that should only come from other programming methods
    // which we have control over (e.g. we already validated user input in
    // the calling method above), so anything using this method shouldn't
    // need to report problems externally, and compilation mode can remove
    // this "unnecessary" check from production

    Debug.Assert(stuff != null);
    Debug.Assert(doohickey != null);
    Debug.Assert(limit > 0);

    /* now do the actual work... */
}

したがって、Eric Lippertらが言ったように、あなた(開発者)が誤って別の場所で間違っ使用した場合に備え、正しいと思われるものだけをアサートするので、コードを修正できます。基本的に、たとえばユーザー入力の場合など、制御できない、または予期できない場合に例外をスローします。これにより、不良データを与えたもの(ユーザーなど)が適切に応答できるようになります。


3つのアサートは完全に冗長ではありませんか?それらのパラメーターをfalseと評価することは不可能です。
David Klempfner、2018年

1
それがポイントです-アサーションは不可能であるものを文書化するためにそこにあります。どうしてそうするか?ReSharperのように、DoSomethingImplメソッド内で「ここでnullを逆参照している可能性がある」という警告を出し、「私は何をしているのかわかっているので、nullになることは決してない」と伝えたい場合があるためです。それは、特に何百行も離れている場合は特に、DoSomethingとDoSomethingImplの間の接続をすぐには理解しない可能性がある一部のプログラマーにとっても示唆です。
マルセルポペスク2018年

4

Code Completeのもう1つのナゲット:

「アサーションは、仮定が真でない場合に大声で不平を言う関数またはマクロです。アサーションを使用して、コードで行われた仮定を文書化し、予期しない条件を洗い流します。...

「開発中、アサーションは矛盾する仮定、予期しない条件、ルーチンに渡される不正な値などを洗い流します。」

彼はさらに何を主張すべきか、何を主張すべきでないかについていくつかのガイドラインを追加します。

一方、例外:

「例外処理を使用して、予期しないケースに注意を引きます。例外的なケースは、開発中に明らかになり、本番用コードの実行中に回復できるように処理する必要があります。」

この本を持っていない場合は、購入する必要があります。


2
私はその本を読みました、それは素晴らしいです。しかし、あなたは私の質問に答えませんでした:)

正解です。答えませんでした。私の答えは「私はあなたに同意しない」です。アサーションと例外は、上記で説明したように異なる動物であり、他の投稿された回答の一部はここにあります。
Andrew Cowenhoven 09/09/23

0

Debug.Assertはデフォルトではデバッグビルドでのみ機能するため、リリースビルドで何らかの予期しない悪い動作をキャッチする場合は、例外を使用するか、プロジェクトプロパティでデバッグ定数をオンにする必要があります(これは一般的には良い考えではありません)。


最初の部分的な文は真であり、残りは一般的には悪い考えです。アサーションは仮定であり、検証は行われず(上記のとおり)、リリースでデバッグを有効にすることは実際にはオプションではありません。
Marc Wittke

0

もののために使用アサーションARE可能ですが、起こるべきではありません(それは不可能だった場合は、アサーションを置く理由は、?)。

それを使用する場合のように聞こえませんExceptionか?なぜアサーションを使用するのExceptionですか?

なぜなら、アサーションのパラメーターがfalseになるのを止めるアサーションの前に呼び出されるコードがあるはずだからです。

通常Exception、スローされないことを保証するコードは、その前にありません。

製品にDebug.Assert()まとめられているのはなぜ良いのですか?デバッグでそれについて知りたい場合は、製品でそれについて知りたくないですか?

開発中にのみ必要です。Debug.Assert(false)状況を見つけたら、それがDebug.Assert(false)再び発生しないことを保証するコードを記述します。開発が完了したら、Debug.Assert(false)状況を見つけて修正したと仮定するDebug.Assert()と、は冗長になるため、安全にコンパイルできます。


0

あなたがかなり大規模なチームのメンバーであり、クラスでのオーバーラップを含め、同じ一般的なコードベースで作業している人が何人かいるとします。他のいくつかのメソッドによって呼び出されるメソッドを作成し、ロックの競合を回避するために、個別のロックを追加するのではなく、特定のロックを使用して呼び出しメソッドによって以前にロックされていたと想定します。たとえば、Debug.Assert(RepositoryLock.IsReadLockHeld || RepositoryLock.IsWriteLockHeld); 他の開発者は、呼び出し側メソッドがロックを使用する必要があるというコメントを見落とす可能性がありますが、これは無視できません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.