コンピュータプログラミング、特に命令型プログラミングパラダイムを用いる場合、アサーションとは、プログラム内の特定の箇所に関連付けられた述語(状態空間上のブール値関数であり、通常はプログラムの変数を用いた論理命題として表現される)であり、コード実行中のその箇所では常に真と評価されるべきものです。アサーションは、プログラマがコードを読みやすくしたり、コンパイラがコンパイルしやすくしたり、プログラム自身が欠陥を検出したりするのに役立ちます。
後者の場合、一部のプログラムは実行時に述語を実際に評価することでアサーションをチェックします。そして、それが実際には真でない場合(アサーション失敗)、プログラムは自身に不具合があると判断し、通常は意図的にクラッシュするか、アサーション失敗例外をスローします。
以下のコードには、2 つのアサーション と が含まれておりx > 0、x > 1実行中の指定された時点で実際に真となっています。
int x = 1 ; assert x > 0 ; x ++ ; assert x > 1 ;プログラマーはアサーションを使用して、プログラムの仕様を定めたり、プログラムの正しさについて推論したりすることができます。たとえば、事前条件(コードセクションの先頭に配置されるアサーション)は、プログラマーがコードの実行を期待する状態の集合を決定します。事後条件(末尾に配置される)は、実行終了時の期待される状態を記述します。例:x > 0 { x++ } x > 1。
上記の例では、CAR Hoareが1969年の論文で使用したアサーションの表記法を使用しています。[ 1 ]この表記法は、既存の主流のプログラミング言語では使用できません。しかし、プログラマーは、プログラミング言語のコメント機能を使用して、チェックされていないアサーションを含めることができます。たとえば、C++では次のようになります。
x = 5 ; x = x + 1 ; // {x > 1}コメントに含まれる括弧は、このコメントの使い方を他の使い方と区別するのに役立ちます。
ライブラリによっては、アサーション機能を提供するものもあります。例えば、C99をサポートするglibcを使用したC言語では、次のようになります。
#include <assert.h>int f ( void ) { int x = 5 ; x = x + 1 ; assert ( x > 1 ); }現代のプログラミング言語の多くは、チェック付きアサーション(実行時または場合によっては静的にチェックされるステートメント)を備えています。アサーションが実行時に偽と評価されると、アサーション失敗が発生し、通常は実行が中断されます。これにより、論理的な矛盾が検出された箇所が明確になり、そうでない場合よりも望ましい動作となる場合があります。
アサーションを使用することで、プログラマーはプログラムの設計、開発、および論理的思考を行うことができます。
Eiffelのような言語では、アサーションは設計プロセスの一部を形成します。一方、CやJavaのような他の言語では、アサーションは実行時に前提条件を検証するためにのみ使用されます。どちらの場合も、アサーションは実行時に妥当性をチェックできますが、通常は抑制することも可能です。
アサーションはドキュメントの一種として機能します。アサーションは、コードが実行される前に期待される状態(前提条件)と、実行が終了したときに期待される状態(事後条件)を記述できます。また、クラスの不変条件を指定することもできます。Eiffelはこのようなアサーションを言語に統合し、自動的に抽出してクラスをドキュメント化します。これは、契約による設計手法の重要な部分を形成します。
この方法は、明示的にサポートしていない言語でも有効です。コメント内のアサーションではなく、アサーション文を使用する利点は、プログラムが実行されるたびにアサーションをチェックできることです。アサーションが成り立たなくなった場合は、エラーを報告できます。これにより、コードとアサーションの同期が崩れるのを防ぐことができます。
例えば、以下はC++における契約による設計(C++26契約を使用)の例です。[ 2 ] [ 3 ]
int f ( const int x ) pre ( x != 1 ) // 事前条件アサーションpost ( r : r == x && r != 2 ) // 事後条件アサーション。r は f の結果オブジェクトの名前です{ contract_assert ( x != 3 ); // アサーション文return x ; }アサーションは、プログラマがプログラムの実装中に立てた仮定が、プログラムの実行時にも有効であることを検証するために使用できます。たとえば、次のJavaコードを考えてみましょう。
int total = countNumberOfUsers (); if ( total % 2 == 0 ) { // total は偶数} else { // total は奇数で非負の値assert total % 2 == 1 ; }Javaでは、は剰余演算子(モジュロ%)であり、Javaでは、最初のオペランドが負の場合、結果も負になる可能性があります(数学で使用されるモジュロとは異なります)。ここでは、プログラマーは が非負であると仮定しているため、2で割った余りは常に0または1になります。アサーションはこの仮定を明示的に示しています。 が負の値を返す場合、プログラムにバグがある可能性があります。totalcountNumberOfUsers
この手法の大きな利点は、エラーが発生した場合、後になってしばしば不明瞭な影響を通してではなく、即座に直接的に検出できることです。アサーションの失敗は通常、コード上の場所を報告するため、多くの場合、追加のデバッグを行うことなくエラーを特定できます。
アサーションは、実行が到達するはずのない箇所に配置される場合もあります。例えば、C、C++、Javaなどの言語では、ステートメントdefaultの句にアサーションを配置することができます。プログラマが意図的に処理しないケースではエラーが発生し、プログラムはエラー状態のまま黙って実行を続けるのではなく、異常終了します。D言語では、ステートメントに句が含まれていない場合、このようなアサーションが自動的に追加されます。switchswitchdefault
Javaでは、バージョン1.4以降、アサーションが言語の一部となっています。アサーションが失敗するとAssertionError、適切なフラグを指定してプログラムを実行すると例外が発生します。フラグが指定されていない場合、アサート文は無視されます。C言語では、アサーションは標準ヘッダーによって追加され、失敗した場合にエラーを通知するマクロとして定義され、通常はプログラムを終了します。C ++では、およびヘッダーの両方がマクロを提供します。<assert.h>assert(assertion)<assert.h><cassert>assert
アサーションの危険性は、メモリデータの変更やスレッドタイミングの変更によって副作用を引き起こす可能性があることです。アサーションは、プログラムコードに副作用を与えないよう、慎重に実装する必要があります。
言語におけるアサーション構造を用いることで、サードパーティライブラリを使用せずに容易にテスト駆動開発(TDD)を行うことができる。
C#にはアサーション マクロやキーワードはありませんが、代わりにメソッドを提供するクラスSystem.Diagnostics.Debugがあります。System.Diagnostics.TraceAssert()
Rustにはマクロがありますassert!()。
開発サイクル中、プログラマーは通常、アサーションを有効にしてプログラムを実行します。アサーション違反が発生すると、プログラマーはすぐに問題が通知されます。多くのアサーション実装では、プログラムの実行も停止します。これは、アサーション違反が発生した後もプログラムの実行が継続されると、プログラムの状態が破損し、問題の原因特定が困難になる可能性があるため、非常に有用です。アサーション違反によって提供される情報(例えば、違反が発生した場所やスタックトレース、あるいは環境がコアダンプをサポートしている場合やプログラムがデバッガで実行されている場合はプログラムの状態全体など)を使用することで、プログラマーは通常、問題を修正できます。このように、アサーションはデバッグにおいて非常に強力なツールとなります。
プログラムを本番環境にデプロイする場合、通常、アサーションはオーバーヘッドや副作用を避けるために無効になります。場合によっては、C/C++ のマクロによるアサーションのように、デプロイされたコードからアサーションが完全に除外されることもあります。Java のように、デプロイされたコードにアサーションが存在し、デバッグのために現場で有効にできる場合もあります。[ 4 ]
アサーションは、特定のエッジ条件が実際には到達不可能であることをコンパイラに保証するためにも使用でき、それによって、そうでなければ不可能な特定の最適化が可能になります。この場合、アサーションを無効にすると、実際にはパフォーマンスが低下する可能性があります。
コンパイル時にチェックされるアサーションは、静的アサーションと呼ばれます。
静的アサーションはコンパイル時のテンプレートメタプログラミングで特に役立ちますが、アサーションが失敗した場合(かつその場合に限り)に不正なコードを導入することで、C のような低レベル言語でも使用できます。C11とC ++11 は、静的アサーションを直接サポートしていますstatic_assert。以前の C バージョンでは、静的アサーションは、たとえば次のように実装できます。
#define SASSERT(pred) switch(0){case 0:case pred:;}SASSERT (ブール条件)この部分が false と評価された場合(BOOLEAN CONDITION)、コンパイラは同じ定数を持つ2 つのcase ラベルを許可しないため、上記のコードはコンパイルされません。ブール式はコンパイル時定数である必要があります。たとえば、 はそのコンテキストでは有効な式です。この構造はファイル スコープ (つまり、関数内) では機能しないため、関数で囲む必要があります。(sizeof(int)==4)
C言語でアサーションを実装するもう一つの一般的な方法[ 5 ]は次のとおりです。
static char const static_assertion [ ( BOOLEAN CONDITION ) ? 1 : -1 ] = { '!' };(BOOLEAN CONDITION)この部分が false と評価された場合、配列の長さは負の値を取ることができないため、上記のコードはコンパイルされません。実際には、コンパイラが負の長さを許容する場合、初期化バイト (この'!'部分) によって、そのような寛容すぎるコンパイラでもエラーが発生するはずです。ブール式はコンパイル時の定数値である必要があります。たとえば、 は(sizeof(int) == 4)そのコンテキストでは有効な式です。
これらの方法はいずれも、一意の名前を構築する方法を必要とします。最新のコンパイラは、__COUNTER__コンパイル単位ごとに単調増加する数値を返すことで一意の名前の構築を容易にするプリプロセッサ定義をサポートしています。[ 6 ]
ほとんどのプログラミング言語では、アサーションをグローバルに、場合によっては個別に有効または無効にすることができます。アサーションは、開発中は有効にし、最終テスト時や顧客へのリリース時には無効にすることがよくあります。アサーションをチェックしないことで、アサーションの評価コストを回避できます。また、(アサーションに副作用がないと仮定すれば)通常の状態では同じ結果が得られます。異常な状態では、アサーションチェックを無効にすることで、本来なら異常終了するはずのプログラムが実行を継続できる場合があります。これは、場合によっては望ましいことです。
C、YASS、C++などの一部の言語では、プリプロセッサを使用してコンパイル時にアサーションを完全に削除できます。
同様に、引数として「-O 」(「optimize」の略)を指定してPythonインタープリタを起動すると、Pythonコードジェネレータはassert用のバイトコードを出力しなくなります。 [ 8 ]
Javaでは、アサーションを有効にするために、実行時エンジンにオプションを渡す必要があります。このオプションがない場合、アサーションはバイパスされますが、JITコンパイラによって実行時に最適化されて削除されるか、プログラマが各アサーションを句の後ろに手動で配置してコンパイル時に除外されないif (false)限り、コード内に常に残ります。
プログラマーは、言語の通常のアサーションチェック機構を回避または操作することで、常に有効なチェック機能をコードに組み込むことができる。
アサーションは、通常のエラー処理とは異なります。アサーションは論理的にあり得ない状況を記録し、プログラミングエラーを発見します。あり得ない状況が発生した場合、プログラムに根本的な問題があることは明らかです。これはエラー処理とは異なります。ほとんどのエラー状態は起こり得るものですが、中には実際に発生する可能性が極めて低いものもあります。アサーションを汎用的なエラー処理メカニズムとして使用するのは賢明ではありません。アサーションではエラーからの回復ができず、アサーションの失敗は通常、プログラムの実行を突然停止させます。また、アサーションは多くの場合、本番環境のコードでは無効になっています。さらに、アサーションはユーザーフレンドリーなエラーメッセージを表示しません。
エラー処理にアサーションを使用する以下の例を考えてみましょう。
int * ptr = ( int * ) malloc ( sizeof ( int ) * 10 ); assert ( ptr ); // ptr を使用...ここで、プログラマはメモリが割り当てられていない場合はポインタmallocが返されることを認識しています。これは起こり得ます。オペレーティングシステムは、へのすべての呼び出しが成功することを保証していません。メモリ不足エラーが発生した場合、プログラムは直ちに異常終了します。アサーションがない場合、プログラムはが逆参照されるまで実行を続け、使用されている特定のハードウェアによっては、さらに長くなる可能性があります。アサーションが無効になっていない限り、即時終了が保証されます。しかし、グレースフルな失敗が必要な場合は、プログラムで失敗を処理する必要があります。たとえば、サーバーには複数のクライアントが存在する場合や、正常に解放されないリソースを保持している場合、またはデータストアに書き込むコミットされていない変更がある場合があります。このような場合、突然異常終了するよりも、単一のトランザクションを失敗させる方が良いでしょう。NULLmallocptr
もう一つの誤りは、アサーションの引数として使用される式の副作用に頼ってしまうことです。アサーションの唯一の目的は、常に真であるべき条件が実際に真であることを検証することであるため、アサーションは実行されない可能性があることを常に念頭に置いておく必要があります。したがって、プログラムにエラーがないと判断されリリースされた場合、アサーションは無効化され、評価されなくなります。
前の例の別のバージョンを考えてみましょう。
int * ptr ; // 以下のステートメントは malloc() が NULL を返すと失敗しますが、// -NDEBUG! でコンパイルするとまったく実行されません。assert ( ptr = ( int * ) malloc ( sizeof ( int ) * 10 )); // ptr を使用します: -NDEBUG! でコンパイルすると ptr は初期化されません。 ...これは、戻り値を に代入し、それが であるかどうかを 1 つのステップでチェックする賢い方法のように見えるかもしれませmallocんが、ptr呼び出しと への代入は、条件を形成する式を評価する副作用です。プログラムがエラーなしとみなされてリリースされるときなど、パラメータがコンパイラに渡されると、ステートメントが削除されるため、 は呼び出されず、 は初期化されません。これは、プログラム実行のかなり後の段階でセグメンテーション違反や同様のヌルポインタエラーを引き起こす可能性があり、散発的で追跡が困難なバグの原因となる可能性があります。プログラマは、この問題を軽減するために、同様の VERIFY(X) 定義を使用することがあります。NULLmallocptrassertNDEBUGassert()malloc()ptr
現代のコンパイラは、上記のコードに遭遇すると警告を発する可能性がある。[ 9 ]
1947年、フォン・ノイマンとゴールドスタイン[ 10 ]は、 IASマシンの設計に関する報告書の中で、初期のフローチャートを用いてアルゴリズムを説明し、その中で次のような主張を含めています。「Cがフロー図のある特定のポイントに到達するたびに、1つ以上の束縛変数が特定の指定された値を持つか、特定の特性を持つか、または互いに特定の特性を満たすことが必然的に起こる可能性があります。さらに、そのようなポイントで、これらの制約の妥当性を示すことができます。このため、そのような制約の妥当性が主張されている各領域を、アサーションボックスと呼ばれる特別なボックスで示します。」
プログラムの正しさを証明するための主張法は、アラン・チューリングによって提唱されました。1949年6月24日にケンブリッジで行われた講演「大規模ルーチンのチェック」の中で、チューリングは次のように提案しました。「大規模ルーチンが正しいことを確認するという意味で、どのようにチェックできるでしょうか?チェックする人があまり難しい作業をしなくて済むように、プログラマーは個別にチェックできる明確な主張をいくつか行うべきであり、そこからプログラム全体の正しさが容易に導き出されます。」[ 11 ]
{{cite web}}: CS1 maint: 複数の名前: 著者リスト (リンク)