契約による設計では、責務を遂行する関数に対して事前条件、事後条件を定義し、クラスに対してクラス不変条件を定義します。これら条件を実行時に検証して違反が検出されればエラーを通知(伝播)することで関数の仕様と実行結果が明確になります。
しかし、なかなか使いこなすには難しい概念です。「契約」という概念になじみがないということもあるでしょうし、実際に使うプログラミング言語に契約による設計がない*1ということも大きな理由と思います。
C++言語は契約による設計は言語仕様にないため、実装するには工夫が必要となります。大抵はassertマクロか例外機構を使って構築することになります。そこで、C++での契約による設計をどう実装すればよいかを考えてみます。
契約による設計としてどのような仕組みを実装するか
C++は言語レベルで契約による設計を実現する機能を提供していないため、事前条件、事後条件、クラス不変条件の違反検出方法と検出した場合の通知方法を定義する必要があります。
違反の通知
C言語のassertマクロは、違反検出時はプログラムを中断(abort)させ、コンソールに違反発生ファイル名・行番号・関数名を印字して通知します。
C++例外機構の場合、例外を誰も捕捉しなければプログラムが中断され、コンソールに発生した例外名が印字されます。しかし例外名だけではどこでどんな条件違反が発生したか分かりません。また、例外が途中で捕捉され、そこで何もエラー処理が行われていないと、違反発生が握りつぶされてしまいます。
契約違反は、プログラム設計・実装上のミスあるいは実行環境の問題であり、プログラムはその違反を抱えて続行するよりも、違反発生時点で停止させて原因を追究するのが通常です。例外よりもassertが適しているといえます。
契約による設計の実現機能 | assert | 例外 |
---|---|---|
違反時にプログラムを中断する | 必ず中断 | 握り潰される可能性あり |
違反箇所が特定できる | ファイル名・行番号・関数名が表示 | 例外名のみ |
違反検出機能をOFF | コンパイル時にNDEBUGにより制御 | なし |
契約による設計の条件違反を通知する機構としては、例外よりassertの方が優れています。ただし、違反を検出してもプログラムを続行したい場合(フレームワークで条件違反の捕捉と一括処理をしたい等)、assertでは対応できないので例外を使う必要があるでしょう。
違反の検出
事前条件は、関数の入り口で判定するので一箇所で記述できます。しかし、事後条件とクラス不変条件は、関数の出口で判定するので、関数末尾の一箇所だけ記述すればよいというものではありません。途中でリターンする際や例外発生で関数から抜けるときも判定が必要です(例外で抜ける場合、事後条件判定は不要ですが、クラス不変条件の判定は必須)。
そこで、事後条件については戻り値を返す関数はその戻り値を生成したときに事後条件を検査し(大抵は戻り値生成に失敗したことが事後条件違反)、戻り値がないものは正常リターン時に検査します。
クラス不変条件については、C++では、関数の処理を途中で抜けるときにも必ず後始末処理を実行するためのイディオムとしてRAII(以下URL参照)があるので、これを活用してクラス不変条件の判定を行います。
More C++ Idioms/リソース獲得は初期化である(Resource Acquisition Is Initialization) - Wikibooks
契約による設計の検証ON/OFF
契約による設計によるコードの検証と違反通知を、プログラム実行時は必ず行うのか、それとも検証は開発時のみとするかという定義が必要です。目にした文献の多くは、運用時は検証をOFFするというものでした。(確かに運用時に突然プログラムがアボートしたら問題です)
そこで、契約による設計の検証機能をどう制御するかを検討する必要があります。例えば、C言語のassertマクロは、コンパイル時にNDEBUGが定義されていると無効となります。これをより細かく制御したい場合、自前でassertマクロを作りこむ必要があります。
- 例えば、運用時は検証を行うが違反時は中断ではなくエラーログに記録する