web-dev-qa-db-ja.com

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

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

ただし、assertが発生したが無効にされた状況は、不変条件が壊れたためにアプリケーションがおそらくさらに悪い方法でクラッシュするため、さらに厄介であると私は主張します。

さらに、私にとってパフォーマンスの引数は、それが測定可能な問題である場合にのみカウントされます。私のコードのほとんどのassertsは、

assert(ptr != nullptr);

これはほとんどのコードに小さな影響を与えます。

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

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

22
Nobody

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

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

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

  • いくつかの条件をテストします(そして条件が失敗したスコープで実行を停止します)

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

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

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

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

従来のassertはこの機能を提供しないため、リリースビルドが本番環境にデプロイされるものであると想定すると、リリースビルドには適していません。

これで、このような柔軟性を提供するC標準libにそのような関数やメカニズムがない理由を尋ねることができます。実際、C++には、これらすべての機能(およびその他)を備えた標準メカニズムがあり、それはexceptionsと呼ばれています。

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

21
Doc Brown

アサーションがリリースで有効にされていることを希望する場合は、アサートに間違った作業を行うように依頼しました。

アサートの要点は、リリースでは有効化されていないことです。これにより、開発中に不変条件をテストすることができます。そうしないと、コードを足場にする必要がありました。リリース前に削除する必要があるコード。

リリース中にもテストする必要があると思われるものがある場合は、それをテストするコードを記述します。 _If throw_構文は非常にうまく機能します。他のスローとは異なる何かを言いたい場合は、単に言いたいことを説明する例外を使用します。

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

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

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

リリースでアサートが無効になっているのはなぜですか?アサートは、足場コードの時代の高さで作成されました。本番環境では必要ないことはわかっていたが、バグを見つけるために開発で実行したいと考えていたため、削除する必要があったコード。アサートはif (DEBUG)パターンのよりきれいな代替手段であり、コードを残して無効にできます。これは、テストコードを本番コードから分離するための主な方法として単体テストが始まる前のことです。アサートは、期待を明確にし、ユニットテストよりも優れているケースをカバーするために、エキスパートユニットテスターであっても、今日でも使用されています。

デバッグコードを運用環境に残しておくだけではどうですか。本番コードは会社を困惑させたり、ハードドライブをフォーマットしたり、データベースを破壊したり、大統領を脅迫したメールを送信したりする必要がないためです。つまり、心配する必要がない安全な場所にデバッグコードを記述できるのは素晴らしいことです。

16
candied_orange

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

4
amon

assertは、コメントなどのドキュメントの形式です。コメントと同様に、通常は顧客に出荷しません。リリースコードには含まれません。

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

4
MSalters

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

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

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

3
Nicol Bolas

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

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

3
gnasher729

他の人が指摘したように、assertは、発生してはならないプログラマのミスに対する防御の最後の要塞のようなものです。これらは正常性チェックであり、出荷までに左右に失敗しないことを期待しています。

また、設計は、開発者が有用であると考える理由にかかわらず、安定性のあるリリースビルドから省略されます:美学、パフォーマンス、好きなもの。これは、デバッグビルドとリリースビルドを分離するものの一部であり、リリースビルドにはそのようなアサーションがありません。したがって、類似の「リリースビルドをアサーションでリリース」したい場合、デザインのSubversionがあります。これは、 _DEBUGプリプロセッサ定義であり、NDEBUGは定義されていません。もはやリリースビルドではありません。

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

したがって、主要な領域でそのような健全性チェックを実行するリリースビルドが必要な場合は、ジョブ用の別のツールと、リリースビルドから除外するように設計されていないツールを希望する可能性が高いです。私が個人的に見つける最も便利なのはロギングです。その場合、ユーザーがバグを報告するとき、ユーザーがログを添付し、ログの最後の行から、バグが発生した場所とそれが何であるかについての大きな手がかりが得られれば、作業は非常に簡単になります。次に、デバッグビルドでそれらのステップを再現すると、同様にアサーションエラーが発生する可能性があり、そのアサーションエラーはさらに、時間を合理化するための大きな手掛かりを与えてくれます。ただし、ロギングは比較的コストがかかるため、配列が一般的なデータ構造の境界外にアクセスされていないことを確認するなど、非常に低レベルの健全性チェックの適用には使用しません。アプリケーションのドメインに固有の詳細情報を含む、より高レベルのコンテキストで使用します。

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

このコードがリリース前に削除されるのはなぜですか?チェックはパフォーマンスの低下をあまり引き起こしません。チェックが失敗した場合は、より直接的なエラーメッセージを使用したいという問題が確実にあります。

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

以前のチームでは、デバッグビルドをより高速に実行するためだけに、マトリックスとベクトルライブラリに特定のクリティカルパスの一部のアサートを除外させる必要がありました。関心のあるコードにたどり着くまでに15分待つことを要求し始めました。私の同僚は実際にassertsを完全に削除したかったのです。代わりに、重要なデバッグパスでそれらを回避することで解決しました。これらのクリティカルパスで境界チェックを行わずにベクトル/マトリックスデータを直接使用するようにした場合、完全な操作(ベクトル/マトリックスの計算だけでなく)を実行するのに必要な時間が数分から数秒に短縮されました。つまり、これは極端なケースですが、パフォーマンスの観点からは、断言すらできない場合もあります。

しかし、それもassertsの設計方法にすぎません。全体的にパフォーマンスにそれほど大きな影響がなかった場合は、デバッグビルド機能以上のものとして設計されているか、リリースビルドでも境界チェックを含むvector::atを使用している場合、私はそれを優先するかもしれません。境界アクセス、例えば(まだ大きなパフォーマンスヒットがあります)。しかし、NDEBUGが定義されている場合に省略されるデバッグビルドのみの機能として、私の場合、パフォーマンスに大きな影響を与えるため、現在、私はそれらのデザインがはるかに有用であると感じています。少なくとも私が扱ったケースでは、リリースビルドが実際には最初から失敗してはならないサニティチェックを除外することで、大きな違いが生まれます。

vector::atvector::operator[]

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

一方、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!");に完全に適合します。チェックは最初に例外的なパス内で実行され、通常の実行パスでは何も費用がかからないため、当然のことながら簡単です。

2
user204677

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

つまり、Assertは、一緒に暮らして頻繁に呼び出す必要があるものではありません。これは、二度と起こらないようにするためにsmthを実行する必要があることを示す停止信号です。そして"リリースビルドにアサートがあってはならない"と言うのは "リリースビルドにバグがあってはならない"と言っているようなものです-おい、それはほとんど不可能です、それに対処してください。
または、「エンドユーザーによって作成および実行されたユニットテストの失敗」について考える。ユーザーがプログラムで行うすべてのことを予測することはできませんが、あまりにも深刻な問題が発生した場合は停止する必要があります。これは、ビルドパイプラインを構築する方法と同様です。プロセスを停止し、公開しません。 ?アサーションにより、ユーザーは強制的に停止し、報告し、あなたの助けを待ちます。

2
Nakilon