表明(assertion)とは、
プログラミングにおいて、特定の箇所で必ず真であるべき条件を示す概念です。アサーションとも呼ばれ、プログラムの前提条件を明示するために用いられます。
表明の基本
表明は、プログラムのある時点で真でなければならない式として表現されます。多くの
プログラミング言語では、この前提条件をチェックする仕組みとして表明が実装されています。表明が偽となった場合、それはプログラムに
バグが存在する可能性を示唆します。この状態を「表明違反(assertion failure)」と呼びます。
表明は、プログラマが開発中に
ソースコードへ追加し、
デバッグを効率化し、問題を早期に発見するために利用されます。表明違反が発生した際には、問題の箇所を特定するための追加情報(
ソースコードのファイル名、行番号、スタックトレースなど)が表示されることが一般的です。多くの場合、表明違反が発生するとプログラムの実行は即座に停止します。
具体例
以下に、表明の例を擬似コードで示します。
x > 0 // 表明:xは0より大きい
y = x + 1
x > 1 // 表明:xは1より大きい
この例では、`x > 0`と`x > 1`が表明であり、プログラムの実行中にこれらの条件が真であることが期待されます。このような表明の記法は、
アントニー・ホーアが1969年の論文で採用したものが元になっています。主要な
プログラミング言語では、このような記法が直接採用されることは少ないですが、コメントとして表明を記述することで、コードの意図を明確にすることができます。
例えば、C/
C++では次のように記述できます。
c++
/
{x > 0} /
int y = x + 1;
/
{x > 1} /
このように、コメントを括弧で囲むことで、他のコメントと区別しやすくなります。
表明の使用法
Eiffelなどの言語では、表明は設計段階の一部として組み込まれています。一方、Cや
Javaなどでは、主に実行時の前提条件をチェックする目的で使用されます。いずれの場合も、ソフトウェアの開発時(
デバッグ時)には有効化され、リリース時には無効化されることが多いです。
契約による設計としての表明
表明は、コードの動作を記述する仕様書の一種として捉えることができます。事前条件(コードが実行される前に満たされるべき条件)、事後条件(コード実行後に満たされるべき条件)、クラスの不変条件を記述することができます。
Eiffelでは、このような表明が言語に組み込まれており、クラスの仕様書を自動生成するために使用されます。これは契約
プログラミングの重要な要素です。
契約
プログラミングを直接サポートしない言語でも、表明を使用する価値はあります。コメントではなく表明を使用する利点は、プログラムの実行毎に条件がチェックされる点です。表明が偽になった場合、エラーが表示され、コードの実装と表明との不一致を早期に検出できます。
実行時チェックとしての表明
表明は、プログラマが前提としている条件が、プログラムの実行中にも常に正しいことを保証するために使用されます。例えば、以下の
Javaのコードを見てください。
java
int total = countNumberOfUsers();
int remainder = total % 2;
assert remainder == 0 || remainder == 1;
ここで、プログラマは`total`が負数ではないことを前提としており、`2`で割った剰余は必ず`0`か`1`であると考えています。`assert`文は、この前提条件を明確に示しています。もし`countNumberOfUsers()`が負数を返す可能性がある場合、それはプログラムの
バグとなります。
この手法の主な利点は、問題が発生したときにそれを即座に検出できる点です。表明違反が発生した箇所は特定しやすいので、
デバッグ作業を大幅に効率化できます。また、表明は決して実行されないと想定される箇所にも使用されることがあります。例えば、`switch`文の`default`節に表明を置くことで、予期しない状態が発生した場合にプログラムを停止させることができます。
各言語における表明の実装
- - Java: バージョン1.4から`assert`文として言語の一部となり、表明違反は`AssertionError`を発生させます。
- - C: 標準ヘッダファイル`assert.h`で`assert(expression)`マクロが定義されており、`NDEBUG`シンボルが定義されていない場合に有効になります。表明が失敗すると、エラーメッセージを表示し、`abort()`関数でプログラムを強制終了させます。
- - C++: 標準では`cassert`ヘッダを使用しますが、`assert.h`も利用可能です。
表明は、メモリの内容を書き換えるなどの副作用を持つ可能性があるため、プログラムの動作に影響を与えないように注意して実装する必要があります。
開発サイクルにおける表明
開発中、プログラマは表明を有効にした状態でプログラムを実行します。表明違反が発生すると、問題が即座に通知され、プログラムの実行は停止します。表明違反で表示される情報(違反発生箇所やスタックトレース)を利用することで、プログラマは問題を迅速に解決できます。これにより、
デバッグ作業が効率化されます。
静的表明
コンパイル時にチェックされる表明は静的表明と呼ばれます。静的表明は、コンパイル時に数値型のサイズをチェックする際や、
テンプレートメタプログラミングで特に有効です。
各言語における静的表明の実装
- - D言語: `static assert`をサポートしています。
- - C++: C++11規格で`static_assert`が導入されました。
- - C言語: C11規格で`_Static_assert`が導入されました。
旧規格のC/
C++でも、コンパイルエラーが発生するコードを記述することで、静的表明を実装できます。例えば、
C言語では以下のような方法があります。
c
switch (1) {
case 1:
case (BOOLEAN CONDITION):
;
}
この例では、`(BOOLEAN CONDITION)`が偽と評価されると、コンパイラは同じ値を持つcaseラベルが複数あるとしてエラーを検出します。
別の実装方法として、負のサイズの配列を作成する方法もあります。
c
char array_name[(BOOLEAN CONDITION) ? 1 : -1];
この場合、`(BOOLEAN CONDITION)`が偽と評価されると、配列の長さが負になるため、コンパイルエラーが発生します。
表明の抑止
表明は開発ツールであるため、最終テストやリリース時には無効化されることが一般的です。表明の有効/無効を切り替えても、プログラムの動作に意味的な違いが生じないように注意する必要があります。表明は副作用を持つべきではありません。
多くの言語では、リリースコンパイル時にプリプロセッサによって表明を完全に除去します。
Javaでは、実行時にオプションを指定することで、表明を有効化できます。オプションがない場合は、表明は無視されます。
エラー処理との比較
表明は、論理的にありえない状況をチェックするために使用すべきです。一方、エラー処理は、通常発生しうるエラーを処理するために使用します。表明は、エラーからの復旧を考慮しておらず、表明違反が発生した場合はプログラムが停止します。
例えば、
C言語で`malloc`のエラー処理に表明を使用することは適切ではありません。
c
int
ptr = (int )malloc(sizeof(int) * 10);
assert(ptr != NULL);
`malloc`が`NULL`を返すことはあり得るため、エラー処理を行うべきです。しかし、表明を使用することで、プログラマが`malloc`のエラーに対処しない選択をしたことを明示する効果はあります。
表明の引数に副作用のある式を使用することは避けるべきです。例えば、次のコードは問題を引き起こす可能性があります。
c
assert(ptr = malloc(sizeof(int)));
この場合、表明が無効化されると、`malloc`が呼び出されなくなり、`ptr`が初期化されずに使われる可能性があります。
歴史
表明の概念を最初に提唱したのは
アラン・チューリングです。1949年、彼は「大きなルーチンが正しく動作しているかを確認するために、プログラマは明確な表明をすべきである」と述べています。
まとめ
表明は、
プログラミングにおける重要な概念であり、プログラムの信頼性を高めるために役立ちます。適切な表明の使用は、
デバッグ作業を効率化し、
バグを早期に発見するのに役立ちます。
脚注
[1] 参考文献1
[2] 参考文献2
関連項目
外部リンク
- - The benefits of programming with assertions by Philip Guo (Stanford University), 2008.
- - Java: Programming With Assertions in Java