表明
出典: フリー百科事典『ウィキペディア(Wikipedia)』
表明(ひょうめい、Assertion)とは、プログラミング言語の構文の一種であり、そのプログラムの前提条件を示すのに使われる。アサーションとも呼ばれる。表明は、プログラムのその箇所で必ず真であるべき式の形式をとる。多くの言語ではそのような前提条件のチェックに表明を使用するが、設計上の判断を文書化するのに使う場合もある。表明が偽となった場合、プログラムにバグが潜在していることを示している。これを「表明違反; assertion failure」と呼ぶ。
プログラマは、開発過程でソースコードに表明を追加する。デバッグを単純化し、問題を早期に検出するためである。表明違反はバグを示していることが多いため、表明の実装では問題の元を示すために追加情報を表示するようになっていることが多い(ソースコードのファイル名と行番号、スタックトレースなど)。ほとんどの実装では、そのプログラムの実行が即座に停止する。
目次 |
[編集] 使用法
Eiffelのような言語では、表明は設計工程の一部である。CやJavaでは実行時に前提条件をチェックするだけである。いずれの場合も実行時に正当性をチェックすることができるが、最終的には抑止されることが多い。
[編集] 契約による設計としての表明
表明を仕様書の一種と見ることもできる。コードの部分が動作する前に期待される状態(事前条件)を記述し、そのコードを実行した後に期待される状態(事後条件)を記述する。また、クラスの不変量を記述することもできる。Eiffelではそのような表明は言語に組み込まれており、そのクラスの仕様書の自動生成に使用される。これは契約プログラミングの重要な部分でもある。
この手法は、契約プログラミングを明確にはサポートしていない言語でも利用価値がある。コメントではなく表明を使用する利点は、表明がプログラムの実行毎にチェックされる点である。表明が真でなくなると、エラーが表示される。これによりコードの実装が表明とずれてしまった場合を早期に検出する。これはつまり、コメントとコードの内容の不一致の問題と同じである。
[編集] 実行時チェックとしての表明
表明はプログラマが前提条件としていたことがプログラム実装中にも保持され、プログラム実行時でも正しいことを保証するのに使われる。例えば、以下のJavaコードを見てみよう:
int total = countNumberOfUsers(); if (total % 2 == 0) { // total is even } else { // total is odd assert(total % 2 == 1); }
Javaでは %
は剰余演算子である。その第一オペランドが負であった場合、演算結果も負となる。ここでプログラマは total
が負でないという前提でコーディングしており、2 で割った剰余は常に 0 か 1 だと考えている。表明(assert)は、その前提条件を明確に示している。countNumberOfUsers
が負数を返す可能性があるなら、これはプログラムのバグとなる可能性がある。
この技法の主な利点は、問題が発生したときにそれを即時かつ直接的に検出できる点であり、後から検出しても様々な副作用によって真の原因がなかなかつかめないことがある。表明違反はコード上の位置を表示することが多いので、煩雑なデバッグ作業なしで問題点を即座に発見することができる。
表明は決して実行されないと見なされている箇所に置かれることもある。例えば、C、C++、Javaのような言語で、switch
文の default
節に表明を置くことがある。プログラマが予期しない状態が発生した場合、実行をそのまま続けるのではなく、エラーを発生させてプログラムを停止させるのである。
Javaでは、表明はバージョン 1.4 から言語の一部となった。表明違反は AssertionError
を発生させる。CやC++では標準ヘッダファイルで assert (assertion)
マクロが定義されており、エラーを表示してプログラムを停止させるようになっているのが一般的である。
[編集] 開発サイクル内での表明
開発中、プログラマは表明を入れた状態でプログラムを実行する。表明違反が発生すると、プログラマに即座に問題が通知される。ほとんどの表明の実装ではプログラムの実行も停止する。プログラムがそのまま実行を続けてしまうと問題の原因を究明することが困難となる可能性が高い。表明違反で表示される情報(違反発生箇所やスタックトレース)を使えば、プログラマは容易に問題を解決できる。このようにして表明はデバッグ工程を単純化する。
[編集] 静的表明
コンパイル時にチェックされる表明は静的表明と呼ばれる。D言語は静的表明をサポートしている。
// Our algorithm depends on this static assert(t.sizeof == q.sizeof);
静的表明はコンパイル時のテンプレートメタプログラミングに特に有効である。
[編集] 表明の抑止
表明はイネーブル/ディセーブルできるよう実装されていることが多く、プログラム全体でそれを制御できる。表明を何種類か(事前と事後など)実装している言語では、それらを別々にイネーブル/ディセーブルできるようになっているのが一般的である。表明がディセーブルされると、表明違反は発生しない。表明は本来、開発ツールであると考えられるため、リリース時にはディセーブルされることが多い。このため、バージョンによって表明が入っているプログラムと抑止されているプログラムが存在することになる。したがってその違いがプログラムの意味的な違いを生じないことが前提にある。換言すれば、表明は副作用を持っていてはならない。CやC++での別の手法として、assert
マクロで表明をディセーブルしている場合でも式の評価だけは行うようにすることもある。ただし、これでは表明を抑止しても性能向上につながらない。
製品レベルのコードから表明を除去することも多くの場合自動的に行われる。CやC++ではプリプロセッサで条件を指定すれば表明が展開されないようにできるし、Javaでは実行時エンジンにオプションを渡すことで同様の効果がある。人によってはこれに不安を感じ、反対することがある。それはたとえて言えば、監視員のいるプールで泳いでいた人が突然海で1人で泳ぐようなものだろう。そのような人々は表明をプログラムのフェイルセーフ機構としても使用している。
[編集] エラー処理との比較
表明とエラー処理ルーチンを区別することには意味がある。表明は論理的にありえない状況をチェックするのに使うべきである。もし、「ありえない」ことが起きたとしたら、根本的に何かが間違っていたということである。エラー処理は通常発生しうるエラーを処理する(もちろん、一部の状況はほとんど「ありえない」かもしれない)。表明をあらゆるエラー処理に使用するのは賢明ではない。表明ではエラーからの復旧が考慮されておらず、表明違反は無条件でプログラムを停止させてしまう場合がほとんどである。表明はユーザー向けのエラーメッセージも表示しない。
エラー処理に表明を使っている例を以下に示す。
int *ptr = malloc(sizeof(int) * 10); assert(ptr != NULL); // use ptr
ここで、プログラマは malloc
が NULL
を返す場合があることに気づいている(メモリを確保できなかった場合)。オペレーティングシステムは malloc
が常に成功することは保証していない。したがってプログラムはメモリ確保失敗に対処すべきである。この例では表明は最良の選択ではないだろう。というのも、malloc の失敗は論理的にありえないことではないからである。実際には滅多に発生しないが、設計上考慮すべき可能性である。しかし、このような表明にも利点がある。つまり、プログラマが自分の意思で malloc のエラーに対処するコードを書かない選択をしたことを他の人々に知らしめているのである。