リリースビルドにアサーションがあるべきか


20

assertC ++ のデフォルトの動作は、リリースビルドでは何もしないことです。これはパフォーマンス上の理由で、おそらくユーザーが厄介なエラーメッセージを表示しないようにするために行われたものと思われます。

ただし、assert不変式が壊れているためにアプリケーションがさらに悪い方法でクラッシュする可能性があるため、が発生したが無効になった状況はさらに面倒だと主張します。

さらに、パフォーマンスに関する議論は、それが測定可能な問題である場合にのみカウントされます。assert私のコードのほとんどは、より複雑ではありません

assert(ptr != nullptr);

ほとんどのコードにわずかな影響しか与えません。

これは私に質問を導きます:アサーション(特定の実装ではなく概念を意味する)はリリースビルドでアクティブにする必要がありますか?何故なの)?

この質問は、リリースビルドでアサートを有効にする方法ではないことに注意してください(#undef _NDEBUG自己定義のアサート実装を使用するなど)。さらに、サードパーティ/標準ライブラリコードでアサートを有効にすることではなく、私が制御するコードで有効にします。


ヘルスケア市場で、顧客サイトに病院情報システムのデバッグバージョンをインストールしたグローバルプレーヤーを知っています。また、MicrosoftとC ++デバッグライブラリをインストールする特別な契約を結んでいます。まあ、彼らの製品の品質は...だった
ベルンハルト・ヒラー

2
「アサーションを削除することは、ハーバーで練習するためにライフジャケットを着ることに似ていますが、船が外洋に向けて出発するときにライフジャケットを置き去りにする」という古い格言がありますwiki.c2.com/?AssertionsAsDefensiveProgramming) 。私は個人的にデフォルトでリリースビルドでそれらを有効にしています。残念ながら、この手法はC ++の世界ではあまり一般的ではありませんが、少なくとも有名なC ++のベテランであるJames Kanzeは、常にリリースビルドでアサーションを維持することを支持して主張していました:stackoverflow.com/a/12162195/3313064
Christian Hackl

回答:


20

クラシックassertは、C ++からではなく、古い標準Cライブラリからのツールです。少なくとも後方互換性の理由から、C ++でも引き続き使用できます。

C標準ライブラリの正確なタイムラインは手元にありませんがassert、K&R Cがライブになった直後(1978年頃)に利用可能になったと確信しています。クラシックCでは、堅牢なプログラムを作成するために、NULLポインターテストと配列境界チェックの追加を、C ++よりも頻繁に行う必要があります。ポインターの代わりに参照やスマートポインターを使用することで、NULLポインターテストの総体を回避できますstd::vector。さらに、1980年のパフォーマンスヒットは間違いなく今日よりもはるかに重要でした。だから、「アサート」がデフォルトでデバッグビルドでのみアクティブになるように設計されたのは、おそらくそれが理由だと思います。

さらに、量産コードでの実際のエラー処理では、条件または不変条件をテストし、条件が満たされない場合にプログラムをクラッシュさせる関数は、ほとんどの場合、十分な柔軟性がありません。おそらくデバッグは問題ありません。プログラムを実行してエラーを観察する人は、通常、何が起こるかを分析するために手元にデバッガを持っているからです。しかし、量産コードの場合、賢明なソリューションは機能またはメカニズムである必要があります

  • ある条件をテストします(そして条件が失敗したスコープで実行を停止します)

  • 条件が満たされない場合に明確なエラーメッセージを提供します

  • 外部スコープがエラーメッセージを取得し、特定の通信チャネルに出力できるようにします。このチャネルは、stderr、標準ログファイル、GUIプログラムのメッセージボックス、一般的なエラー処理コールバック、ネットワーク対応のエラーチャネル、または特定のソフトウェアに最適なもののようなものです。

  • ケースごとのベースの外側のスコープで、プログラムを正常に終了するか、続行するかを決定できます。

(もちろん、条件が満たされない場合にすぐにプログラムを終了することが唯一の賢明なオプションである状況もありますが、そのような場合は、デバッグビルドだけでなくリリースビルドでも発生するはずです)。

クラシックassertはこの機能を提供していないため、リリースビルドは本番環境に展開するものであると仮定して、リリースビルドには適していません。

これで、この種の柔軟性を提供するC標準ライブラリにそのような関数やメカニズムがない理由を尋ねることができます。実際、C ++には、これらすべての機能(およびそれ以上)を備えた標準メカニズムがあり、ご存じのように、例外と呼ばれています

ただし、Cでは、プログラミング言語の一部として例外がないため、上記のすべての機能を備えたエラー処理用の適切な汎用標準メカニズムを実装するのは困難です。そのため、ほとんどのCプログラムには、戻りコード、「goto」、「long jump」、またはそれらの混合を使用した独自のエラー処理メカニズムがあります。これらは多くの場合、特定の種類のプログラムに適合する実用的なソリューションですが、C標準ライブラリに適合する「汎用」ではありません。


ああ、Cの遺産を完全に忘れていました(結局のところ#include <cassert>)。これは今では完全に理にかなっています。
誰も

1
私はこの全体の答えに完全に同意しません。ソースコードがバグであることがアプリケーションで検出されたときに例外をスローすることは、Javaなどの言語では改善できない最後の手段ですが、C ++では、このような状況ではプログラムを直ちに終了します。あなたは、スタックが発生する巻き戻したくない任意のその時点で実行されるべき更なる作業を。バグのあるプログラムは、破損したデータをファイル、データベース、またはネットワークソケットに書き込む可能性があります。ただちにクラッシュします(場合によっては、外部メカニズムによってプロセスを再起動します)。
クリスチャンハックル

1
@ChristianHackl:プログラムの終了が唯一の実行可能なオプションである状況に同意しますが、それはリリースモードでも発生するはずであり、assertこれも同様に間違ったツールです(例外を投げることは実際に間違った反応である可能性があります) )。しかし、実際にassertは、C ++では例外を使用するCでの多くのテストにも実際に使用されたと確信しています。
Doc Brown

15

リリースでアサーションが有効になりたいと思う場合は、アサーションに間違った仕事をするように依頼しました。

アサートのポイントは、リリースで有効になっていないことです。これにより、スキャフォールディングコードでなければならないコードを使用して、開発中に不変式をテストできます。リリース前に削除する必要があるコード。

リリース中であってもテストする必要があると思うものがある場合は、それをテストするコードを作成します。If throw構造は非常によく動作します。他のスローとは異なる何かを言いたい場合は、単にあなたが言いたいことを言う説明的な例外を使用してください。

アサートの使用方法を変更できないわけではありません。そうすることはあなたに何も役に立たず、期待に反するものであり、アサートが意図したことを行うためのきれいな方法をあなたに残さないということです。リリースで非アクティブなテストを追加します。

私はアサートの特定の実装についてではなく、アサーションの概念について話している。読者の主張を誤用したり混乱させたりしたくない。そもそもなぜこのようになっているのかを問うつもりでした。追加のrelease_assertがないのはなぜですか?必要はありませんか?assertがリリースで無効化される理由は何ですか?- 誰も

relase_assertがないのはなぜですか?率直に言って、アサートは本番には十分ではありません。はい、必要はありますが、その必要を十分に満たすものはありません。独自に設計できることは確かです。機械的にthrowIf関数は、boolと例外をスローする必要があります。そして、それはあなたのニーズに合うかもしれません。しかし、あなたは本当にデザインを制限しています。それが、言語ライブラリに例外スローシステムのようなベイクドアサートがないことを私が驚かない理由です。それは確かにあなたがそれをすることができないということではありません。他の人が持っています。しかし、問題が発生した場合の対処は、ほとんどのプログラムの作業の80%です。そして、これまでのところ、すべてのソリューションに適した適切なサイズを示した人はいません。これらのケースに効果的に対処することは複雑になる可能性があります。缶詰のrelease_assertシステムが私たちのニーズを満たせなかったら、それはもっと良い害をもたらしたと思います。この問題について考える必要がないことを意味する、優れた抽象化を求めています。私も欲しいのですが、まだそこにいるようには見えません。

リリースでアサートが無効になっているのはなぜですか?アサートは、足場コードの時代の最高時に作成されました。私たちは本番環境でそれを望んでいませんでしたが、バグを見つけるのを助けるために開発で実行したいと思っていたので、削除しなければなりませんでした。アサーションはif (DEBUG)、コードをそのままにして無効にするパターンのよりクリーンな代替手段でした。これは、テストコードを製品コードから分離する主な方法として、単体テストが開始される前でした。アサーションは、期待を明確にし、ユニットテストよりも優れているケースをカバーするために、専門のユニットテスターでも使用されています。

デバッグコードを本番環境にそのまま残さないのはなぜですか?本番コードは、会社を困らせたり、ハードドライブをフォーマットしたり、データベースを破損したり、大統領を脅かす電子メールを送ったりする必要がないためです。要するに、それを心配する必要のない安全な場所でデバッグコードを書くことができるのは素晴らしいことです。


2
私の質問は、なぜリリース前にこのコードを削除する必要があるのでしょうか?チェックはパフォーマンスの低下とは言えません。チェックに失敗した場合は、間違いなく直接的なエラーメッセージを希望します。assertの代わりに例外を使用するとどのような利点がありますか?私はおそらく、無関係なコードがcatch(...)それを可能にしたくないでしょう。
誰も

2
@Nobodyアサートを必要としないためにアサートを使用しないことの最大の利点は、読者を混乱させないことです。コードを無効にしたくない場合は、そのことを示すイディオムを使用しないでください。if (DEBUG)コードのデバッグ以外の何かを制御するために使用するのと同じくらい悪いです。あなたの場合、マイクロ最適化は何の意味もありません。しかし、イディオムは、あなたがそれを必要としないという理由だけで破壊されるべきではありません。
candied_orange

私は自分の意図を十分に明らかにしなかったと思います。私は特定の実装についてではなくassert、アサーションの概念について話している。assert読者を誤用したり混乱させたりしたくない。そもそもなぜこのようになっているのかを問うつもりでした。なぜ追加機能がないのrelease_assertですか?必要はありませんか?assertがリリースで無効化される理由は何ですか?
誰も

2
You're asking for a good abstraction.私はそれについて確信がありません。私は主に、回復がなく、決して起こらない問題に対処したいと考えています。これを一緒に取りBecause production code needs to [...] not format the hard drive [...]、不変式が壊れている場合には、いつでもUBを介したリリースでアサートします。
誰も

1
@Nobody "回復のない問題"は、例外をスローしてキャッチしないことで対処できます。例外のメッセージテキストでは決して発生しないことに注意してください。
candied_orange

4

アサーションはデバッグツールであり、防御的なプログラミング手法ではありません。すべての状況で検証を実行する場合は、条件を記述して検証を実行するか、独自のマクロを作成してボイラープレートを削減します。


4

assertコメントのようなドキュメントの形式です。コメントと同様に、通常は顧客に出荷することはありません。リリースコードには含まれていません。

しかし、コメントの問題は、コメントが古くなる可能性がありますが、コメントが残されることです。そのため、アサートは良いです-デバッグモードでチェックされます。アサートが廃止されると、すぐにそれを発見し、それでもアサートの修正方法を知ることができます。3年前に時代遅れになったそのコメントは?誰もが推測します。


1
したがって、何か問題が発生する前に失敗した不変式で中止することは有効なユースケースではありませんか?
誰も

3

「アサート」をオフにしたくない場合は、同様の効果を持つ単純な関数を自由に記述してください。

void fail_if(bool b) {if(!b) std::abort();}

つまり、出荷された製品でそれらを削除たいassertテスト用です。そのテストをプログラムの定義済み動作の一部にしたい場合は、間違ったツールです。assert


1
私はこれをよく知っています。質問は、デフォルトがそのままである理由の線に沿っています。
誰も

3

assertが何をすべきかを議論するのは無意味です。別の何かが必要な場合は、独自の関数を作成します。たとえば、デバッガで停止するか、何もしないAssertがあり、アプリをクラッシュさせるAssertFatalがあり、状況をアサートして処理できるように結果をアサートして返すブール関数AssertedおよびAssertionFailedがあります。

予期しない問題については、開発者とユーザーの両方がそれを処理するための最良の方法を決定する必要があります。


アサートが何をすべきかについて議論するのは私の意図ではありませんでした。この質問に対する私の動機は、リリースビルドに存在しないアサートの背後にある理由を見つけることでした。なぜなら、私は自分で書き、ユースケース、期待、およびトラップに関する情報を収集しようとしているからです。
誰も

うーん、verify(cond)結果をアサートして返す通常の方法ではないでしょうか?
デュプリケータ

1
私はCではなくRubyでコーディングしていますが、バックトレースを出力してプログラムを停止すると防御的なプログラミング手法としてうまく機能するため、上記のほとんどの回答に同意しません.......私のコメントは適合しませんでした最大コメントサイズ(文字数)なので、以下に別の回答を書きました。
ナキロン

2

他の人が指摘したassertように、プログラマーのミスは絶対に起こらないようにするための最後の防衛の砦です。これらは健全性チェックであり、出荷時に左右に失敗しないことを期待しています。

また、安定性のあるリリースビルドから除外するように設計されています。これは、開発者が便利だと思う理由(美学、パフォーマンス、好きなもの)に関係ありません。これは、デバッグビルドとリリースビルドを分離するものの一部であり、定義により、リリースビルドにはそのようなアサーションがありません。したがって、アナロジー的な「アサーションを備えたリリースビルド」をリリースしたい場合、デザインのサブバージョンがありますこれは、_DEBUGプリプロセッサ定義が定義されていないリリースビルドの試みNDEBUGです。もはやリリースビルドではありません。

デザインは標準ライブラリにも拡張されます。多数の中の非常に基本的な例として、多くの実装でstd::vector::operator[]assert、範囲外のベクトルをチェックしていないことを確認するための健全性チェックが行われます。また、リリースビルドでこのようなチェックを有効にすると、標準ライブラリのパフォーマンスが大幅に低下します。vector使用のベンチマークoperator[]そして、単純な古い動的配列に対してそのようなアサーションを含むフィルクターは、そのようなチェックを無効にするまで動的配列がかなり高速であることを示すことが多いため、些細な方法からはほど遠いパフォーマンスに影響を与えることがよくあります。ここでのヌルポインターチェックと境界外チェックは、スマートポインターの逆参照や配列へのアクセスなどの単純なコードに先行するクリティカルループ内のすべてのフレームでこのようなチェックが何百万回も適用されている場合、実際に莫大な費用になる可能性があります。

そのため、重要な領域でこのような健全性チェックを実行するリリースビルドが必要な場合は、ジョブに別のツールと、リリースビルドから除外するように設計されていないツールを希望する可能性が高くなります。私が個人的に見つけた最も有用なものは、ロギングです。その場合、ユーザーがバグを報告するときに、ログを添付すると事態がずっと簡単になり、ログの最後の行からバグの発生場所とその可能性に関する大きな手がかりが得られます。次に、デバッグビルドで手順を再現すると、同様にアサーションエラーが発生する可能性があり、そのアサーションエラーにより、時間を効率化するための大きな手がかりが得られます。ただし、ロギングは比較的高価なので、汎用データ構造内の範囲外で配列にアクセスしないようにするなど、極端に低レベルの健全性チェックを適用するために使用しません。

それでも最後に、そしてある程度あなたと同意して、アルファテスト中にデバッグビルドに似たものをテスターに​​実際に渡したい合理的なケースを見ることができました、例えば、NDAに署名したアルファテスターの小さなグループと。そこで、実行可能なテストやソフトウェア実行中のより詳細な出力などのデバッグ/開発機能とともにデバッグ情報が添付されたフルリリースビルド以外の何かをテスターに​​渡すと、アルファテストが合理化される可能性があります。少なくともいくつかの大手ゲーム会社がアルファのためにそのようなことをしているのを見てきました。しかし、それはアルファ版や内部テストのようなもので、テスターに​​リリースビルド以外の何かを提供しようとしているのです。実際にリリースビルドを出荷しようとしている場合は、定義上、_DEBUG 定義されているか、そうでなければ「デバッグ」ビルドと「リリース」ビルドの違いを本当に混乱させています。

リリース前にこのコードが削除されるのはなぜですか?チェックはパフォーマンスの低下とは言えません。チェックに失敗した場合は、間違いなく直接的なエラーメッセージを希望します。

上記で指摘したように、チェックは必ずしもパフォーマンスの観点から些細なものではありません。多くはささいなものですが、標準ライブラリでも使用されているため、多くの場合std::vector、最適化されたリリースビルドであると想定されるランダムアクセストラバーサルの4倍の時間がかかると、多くの場合、許容できない方法でパフォーマンスに影響を与える可能性があります境界チェックのため、失敗することはありません。

前のチームでは、デバッグビルドを高速に実行するために、特定のクリティカルパスの一部のアサートをマトリックスとベクトルライブラリから除外する必要がありました。目的のコードを追跡する前に、15分間待機する必要があります。私の同僚は実際にassertsただそれをするだけで大​​きな違いが生じるとわかったからです。代わりに、クリティカルデバッグパスがそれらを避けるようにするだけで解決しました。これらのクリティカルパスで境界チェックを経由せずにベクター/マトリックスデータを直接使用するようにした場合、完全な操作(ベクター/マトリックス演算以上のものを含む)を実行するために必要な時間が数分から秒に短縮されました。したがって、それは極端な場合ですが、間違いなく、パフォーマンスの観点から断言が常に無視できるわけではなく、近いというわけでもありません。

しかし、それはまさにasserts設計された方法でもあります。全体にそれほど大きなパフォーマンスへの影響がなかった場合、デバッグビルド機能以上のものとして設計されている場合、またはvector::atリリースビルドでも境界チェックを含むものを使用し、範囲外でスローする場合、私はそれを好むかもしれませんアクセス(例:パフォーマンスが大幅に低下します)。しかし、現時点でNDEBUGは、定義時に省略されるデバッグビルドのみの機能として、私の場合のパフォーマンスへの大きな影響を考慮して、デザインがはるかに有用であると感じています。少なくとも私が取り組んできたケースでは、リリースビルドが実際に失敗することのない健全性チェックを除外することで、大きな違いが生じます。

vector::atvector::operator[]

これらの2つの方法の違いは、これと代替の例外の中心にあると思います。vector::operator[]通常assert、実装は、境界外のベクトルにアクセスしようとしたときに、境界外アクセスが簡単に再現可能なエラーをトリガーすることを確認します。しかし、ライブラリの実装者は、最適化されたリリースビルドで1ドルもかかりませんという前提でこれを行います。

一方vector::at、リリースビルドでも常に範囲外のチェックとスローを行いますが、パフォーマンスのペナルティがあり、使用するコードvector::operator[]よりもはるかに多くのコードを使用することがよくありますvector::at。C ++の設計の多くは、「使用/必要に応じて支払う」という考え方を反映しており、多くの人がよく支持しoperator[]ます。最適化されたリリースビルドで境界チェックを行う必要はありません。突然、リリースビルドでアサーションが有効になった場合、これら2つのパフォーマンスは同じになり、ベクトルの使用は常に動的配列よりも遅くなります。したがって、アサーションの設計と利点の大部分は、リリースビルドでアサーションがフリーになるという考えに基づいています。

release_assert

これらの意図を発見した後、これは興味深いです。当然、全員のユースケースは異なりますrelease_assertが、チェックを行い、リリースビルドでもソフトウェアをクラッシュさせて行番号とエラーメッセージを表示する方法を使用すると思います。

私の場合、例外がスローされた場合のようにソフトウェアを正常に回復させたくないという不明瞭なケースになります。そのような場合はリリースでもクラッシュして、ソフトウェアが発生しないはずの何かに遭遇したときに報告する行番号をユーザーに与えることができます。例外はありますが、リリースのコストを気にせずに実行できるほど安価です。

私は行番号とエラーメッセージとハードクラッシュ見つけるだろういくつかの例実際に存在することが好ましい優雅リリースに保つために安価に十分であるかもしれないスローされた例外からの回復には。また、既存のものから回復しようとしたときにエラーが発生したなど、例外から回復できない場合があります。そこrelease_assert(!"This should never, ever happen! The software failed to fail!");では、最初に例外的なパス内でチェックが実行され、通常の実行パスでは何も費用がかからないので、当然ながらダート安い安価なものに最適です。


私の意図は、サードパーティのコードでアサートを有効にすることではありませんでした。明確な編集を参照してください。私のコメントからの引用では、私の質問からコンテキストを省略しています。Additionally, the performance argument for me only counts when it is a measurable problem.したがって、アイデアは、リリースからアサーションをいつ取り出すかをケースバイケースで決定することです。
誰も

最後のコメントの問題の1つは、標準ライブラリが、マクロを使用するときに使用するものassertと同じ_DEBUG/NDEBUGプリプロセッサ定義に依存するものを使用することですassert。そのため、たとえば標準ライブラリを使用すると仮定して、標準ライブラリに対しても有効にせずに、1つのコードだけに対してアサーションを選択的に有効にする方法はありません。

STLイテレータチェックがあり、これを個別に無効にすると、すべてではなく一部のアサーションが無効になります。

それは基本的に粒度の粗いツールであり、常にリリースで無効化されるという設計意図があるため、チームの仲間は、リリースのパフォーマンスに影響を与えないという仮定に反して、重要な領域でそれを使用するかもしれません、標準ライブラリはそれを使用します重要な分野では、他のサードパーティのライブラリがそのように使用します。また、何かが必要な場合は、具体的にコードの一部を無効化/有効化し、もう一方は無効にしますassert

vector::operator[]またvector::at、たとえば、リリースビルドでアサーションが有効になっている場合、実質的に同じパフォーマンスが得られoperator[]ます。とにかく境界チェックを行うため、パフォーマンスの観点から使用する意味がなくなります。

2

私はC / C ++ではなくRubyでコーディングしているので、アサートと例外の違いについては触れませんが、ランタイムを停止するものとしてそれについて話したいと思います。バックトレースを出力してプログラムを停止すると、防御的なプログラミング手法としてうまく機能するため、上記の回答のほとんどに同意しません
アサートルーチン(絶対に構文的に記述されていて、「アサート」という単語がプログラミング言語またはdslで使用または存在する場合)を呼び出す方法がある場合は、何らかの作業を行う必要があり、製品すぐに「リリース済み、使用準備完了」から「パッチが必要」に戻ります。実際の例外処理に書き換えるか、間違ったデータが表示されるバグを修正します。

Assertは、あなたが一緒に住み、頻繁に呼び出すべきものではないことを意味します。「リリースビルドにアサートを含めるべきではない」言うことは、「リリースビルドにバグを含めるべきではない」と言うことです。
または、「エンドユーザーによるユニットテストの実行と失敗」について考えてください。ユーザーがプログラムで行うすべてのことを予測することはできませんが、smthが深刻すぎる場合は停止する必要があります-これはビルドパイプラインを構築する方法に似ています-プロセスを停止し、公開しないでください?アサーションにより、ユーザーは強制的に停止し、報告し、ヘルプを待ちます。

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