nfoQでは昨年からC#をベースにして契約による設計をサポートするSpec#(リンク)を取り上げている。契約による設計は静的型付けのように、コンパイル時に検証されないと一定の動作を実行することができないという考え方だ。契約は通常、事前条件・事後条件という形を取る。例えば引数や戻り値がnullでない、あるいは一定の範囲内の値であるという条件がそうだ。
Spec#のような完全に新しい言語の学習がディベロッパの負担にならないように、Microsoftは言語を意識しなくてもいいようにライブラリを開発していて、これはどの.NET言語でも利用することができる。さて、ある種の契約はアサーションに似ているがその実はかなり異なっている。契約は静的なコード解析を組み合わせているため、コンパイラの内部でも外部でも契約を用いることができ、テストフレームワークも利用できる。また契約は実行可能であり、それゆえデバッグバージョン時にはアサーションのように振る舞う。次の例について考えてみよう。
string GetDescription(int x){ Contract.Requires(x>0); Contract.Ensures(Contract.Result() != null);
メソッドの宣言部分(シグネチャ)から、静的な型情報として「GetDescriptionはInteger型の引数を取り、String型の値を返す」ということがディベロッパにもわかる。さらに契約(Contract)が加わると、ディベロッパ、そしてコンパイラなどのツールは「GetDescriptionは正のInterger型の値を取り、nullでないString型の値を返す」ということが分かるようになる。
I明示的な契約に加え、契約チェッカは暗黙の契約もサポートする。そのような例としてゼロでの割り算がある。もしあるクラスでの処理に整数の割り算があって、その際に変数を除数に使う場合、全てのコードでその変数がゼロであってはならず、もしゼロになることがあるのなら警告を出すようにしないといけない。もしその変数がprivateプロパティでない場合は、全てのサブクラスもチェックする必要がある。他にもnullポインタ参照や配列のインデックスといったことに対する暗黙の契約がある。
このようなことを簡単におこなうために、 ObjectInvariant(invariantは不変という意味)メソッドという仕組みがある。この特別なメソッドは契約だけを持ち、これを各メソッドの最後に挿入して目的のオブジェクトの状態が変わってないかを確かめるのに使う。ここで大事なのはこれを全てのメソッド-関係するサブクラスのメソッドも含めて-に適用されるということだ。
他にも時間の節約になる仕組みがある。それは以前の値を簡単に取得できることだ。次の例ではEnsure(確認)契約とOldValue構文が一緒に使われている。こうしてCountプロパティがインクリメントされることを確実にする。
Public Sub Add(value as Object) Contract.Ensure(Count = Contract.OldValue(Count) + 1)
この契約はメソッドの一番上に書かれているが、コンパイラによって自動的にReturn文の直前に移動される。またCountの以前の値を保存するのには幾分オーバーヘッドがあるため、このようなチェックはデバッグバージョンだけでおこなわれる。
ライブラリディベロッパをサポートすることに関しては、リリースバージョンではある参照ライブラリが含まれるというものがある。たとえばWidgets.dllというライブラリに含まれている契約はWidgets.Contracts.dllというライブラリへ別出しにされる。このことでクライアントディベロッパがリリース環境に近い環境を早い時期に利用できるようになり、かつライブラリディベロッパが作った契約も利用できるようになる。
さらに興味深い機能のひとつは、これが実装クラスの関数にだけ適用されるのではないということだ。実装を持たないインターフェースや抽象メソッドも契約をもつことができるのだ。これはそのインターフェースについての参照実装を、契約を保持するためだけに作ることで実現される。この参照実装は属性によってインターフェースへリンクされることになる。
契約の内容に制限はない。そして同じ内容の契約を静的なチェックにも実行時の契約にも使うことができるので、一方だけでは評価しきれない複雑な契約をもう一方でチェックすることもできる。また契約はドキュメントジェネレータが抽出して使えるようにもなっている。
.NET 4での契約についてのより詳しい情報については、このPDC基調講演(リンク)の前半部分をチェックしてほしい。