はじめに
今回の内容は契約プログラミングです。
はて、契約とは何でしょう。
つまるところ呼び出し側と呼び出される側がそれぞれ満たすべき条件を明記することで、どちらがどこまで責任を負うのかを白黒はっきりさせることができます。 また、C#やVBでは Code Contracts を利用すれば条件が正しく満たされるようにコード中でチェックを行うのを動的・静的に支援してくれます。
Code Contracts for .NET
.NET FrameworkでSystem.Diagnostics.Contracts名前空間のクラスを利用して契約プログラミングを行うにはバイナリリライターなどのツールが別途必要になります。
なので、サクッとインストールしましょう。 あっ、ここで一つ注意ですがインストールした結果 Visual Studio が不安定になっても責任は負えないので自己責任ってことでお願いします。
Code Contracts for .NET - Visual Studio Marketplace
なお、下の方に書いてあるのですが、
It installs in any edition of Visual Studio other than the Express edition.
ってことみたいなので、Express エディションの人はお疲れさまでした。 いやまぁ、個人ならCommunity エディション使えばいいだけの話ですが。
インストールするとプロジェクトのプロパティにCode Contractsという項目が追加されたはずです。
はじめての契約
取り敢えずプロジェクトを作成したら『参照』からインポートされた名前空間のところでSystem.Diagnostics.Contractsを追加しておきましょう。
プロジェクトの形態によって使うメソッドが変わるのですが、取り敢えずは引数チェックはリリース時には取り除かれるという前提で進めます。
他には
- 引数チェックのコードはリリースコードにも残したままにするが Code Contracts Rewriter(以下CCリライター)が存在する環境でリリースビルドを行う。
- 引数チェックのコードをリリースコードに残したままにするし、CCリライターが無い環境でリリースビルドを行う。
などがあります。(あるみたいです。)
Public Class Calculator Public Shared Function PositiveSubtract(a As Integer, b As Integer) As Integer ' 呼び出し側が満たすべき条件 Contract.Requires(a > b) ' 呼び出される側が満たすべき条件 Contract.Ensures(Contract.Result(Of Integer)() > 0) Return a - b End Function End Class
Module Module1
Sub Main()
Console.WriteLine(Calculator.PositiveSubtract(1, 2))
End Sub
End Module
どうでしょう。正常にコンパイルと実行ができたでしょうか。 えっ、失敗しないとおかしいじゃないかって。 そうなんですけど、そうじゃないって言うか。
さっきの Code Contracts の所で設定をしないといけません。
- Assembly Mode → 『Standard Contract Requires』
- Perform Runtime Contract Checking → 『Full』
- Perform Static Contract Checking → チェックを入れる
- Contract Reference Assembly → 『Build』
細かい設定などについてはマニュアルをどうぞ
http://research.microsoft.com/en-us/projects/contracts/userdoc.pdf
えっ?英語が読めないって? 奇遇ですね。弊社もなんです。
今回のようにリリースビルドで引数チェックを取り除く場合はContracts.Requiresを用いて、その場合 Assembly Mode は『Standard Contract Requires』を選択します。
また、Perform Runtime Contract Checking は『Full』で、Perform Static Contract Checking はチェックを入れ細かい設定はデフォルトで行きます。
Code Contractsを利用する場合はContract Reference Assembly は『Build』を選択します。
さて、この状態でリビルドをしてみましょう。 出力にずらずら何かが表示されてたはずです。
ここで、
Console.WriteLine(Calculator.PositiveSubtract(1, 2))
の箇所を見ると、下線が引かれ
Code Contracts: requires is false: a > b
と表示されているはずです。
性的静的な契約のチェックが行われ、コンパイル時に検出できるものはこのように表示してくれるっぽいです。
静的なチェックはコンパイラエラーでは無い為、実行することができます。
が、実行時に契約違反としてContract.Requires(a > b)の箇所から例外がスローされます。
では契約を守るようコードを修正しましょう。
Console.WriteLine(Calculator.PositiveSubtract(1, 2))
を
Console.WriteLine(Calculator.PositiveSubtract(2, 1))
と書き換えると、契約が満たされますから静的なチェックでも動的なチェックでも引っかかることなく実行できます。
次に、呼び出される側の契約を違反させてみましょう。
Return a - b
を
Return a - b - 10
と書き換えて、リビルドしてみましょう。
さっきの部分が
CodeContracts: Missing precondition in an externally visible method. Consider adding Contract.Requires((a - b - 10) > 0); for parameter validation
と表示されます。 これはメソッドの公開されていない事前条件によって事後条件が失敗するかもしれんよってことです。 もちろん、呼び出し側で指定している値では事後条件を満たせませんから実行時に例外がスローされます。
今回はメソッド内でなんとかしましょう。 先ほどの部分を
Return Math.Abs(a - b - 10)
に書き換えると静的及び動的なチェックをパスするようになります。*1
まとめ
今回はクッソ適当な初期のビルド設定と単純な事前条件と事後条件の例をやりました。 これだけでも、弊社は特に静的なコードチェックは有用だな〜と感じました。
より発展的な内容は次回以降にやりたいと思います。
つづく
*1:静的コードチェッカは Math.Abs が正の値を返すことを理解しているのでしょうか? それともチェックをしてないだけ?