C#での契約プログラミング

オブジェクト指向クラス設計原則のSOLIDのL(LSP:リスコフ置換原則)は、一言で言うなら 『サブクラスはスーパークラスと等価に扱えないといけない』 ということ。なんでそれが必要かというと、親クラスと子クラスで同じ関数に対する振る舞いが違うと、ポリモーフィックに扱えなくなり、その結果実行時の型情報(RTTI)を使った処理を書くハメになる、そうすると継承のメリット・必要性が失われるから。

インターフェースも継承も契約なため、静的な型付けをベースとするC#に「契約プログラミングは」は適していると思います。それが.NET4.0から標準に近い形で組込まれていたことを知りましたのでご紹介。

Code Contract for.NETをダウンロード & インストール。

codecontract_install

インストールはNext/Accept連打でOKです。これをインストールするとVisualStudioのプロジェクトのプロパティに [Code Contracts] タブが追加されます。ここで契約の度合いを設定します。

project_page

こちらは後述するとして、契約の例を見ていきます。契約には ①前提条件、②事後条件、③不変条件 の3種類があります。例を見ながら確認します。

1.前提条件を成立させる

PreconditionSample.cs

namespace CodeContractSample
{
    using System;
    using System.Diagnostics.Contracts;

    internal class PreconditiionSample
    {
        internal void ForbidNull(object arg)
        {
            Contract.Requires(arg != null);
            Console.WriteLine("ForbidNull called.");
        }

        internal void ForbidnullAndThrowsException(object arg)
        {
            Contract.Requires<ArgumentNullException>(arg != null);
            Console.WriteLine("ForbidnullAndThrowsException called.");
        }

        internal void ForbidNullWithLegacyCheck(object arg)
        {
            if (arg == null) throw new ArgumentNullException("arg");
            Contract.EndContractBlock(); // これより上をコントラクトと扱う.
            Console.WriteLine("ForbidNullWithLegacyCheck called.");
        }
    }
}

前提条件は関数を読みだしたときの条件で、Contract.Requires静的メソッドで表します。

1つ目はそのまま引数をコントラクトとして扱う最も単純な形です、この契約が守られていない場合ContractExceptionがスローされます。2つ目は同じく引数にコントラクトを取りますが、コントラクトが満たされなかった場合スローする例外の型を取ります。3つ目はCode Contractを使わない方法で、EndContractBlock()より上の内容をコントラクトとして扱います。上のようなガード節を使った書き方が決まっています、詳しくはDocmentを参照ください。

 

2.事後条件を成立させる

Postconditionample.cs

namespace CodeContractSample
{
    using System;
    using System.Diagnostics.Contracts;

    internal class PostconditionSample
    {
        private int testField = 0;
        internal void AssertZero()
        {
            Contract.Ensures(testField == 0);
            testField = 1;
        }

        internal void AssertZeroAndThrowsException()
        {
            Contract.EnsuresOnThrow<DivideByZeroException>(testField == 0);
            testField = 1;
            testField = testField / (testField - testField);
        }
    }
}

事後条件としては関数を抜けた時に満たしている条件で、Contract.Ensuresで表します。関数の先頭にありますが、関数を抜けるタイミングで評価されますのでご安心ください。

1つ目はtestFieldが0で関数が終わることをコントラクトにしていますが、1で抜けるのでContractExceptionがスローされます。2つ目は指定した例外がスローされた場合であっても守られる条件をコントラクトにしています。testFieldに1をセットした後0割で例外が投げられますが、その時でも0であることをコントラクトにしていますが満たしていないので同じくContractExceptionがスローされます。

 

3.不変条件を成立させる

 InvariantSample.cs

namespace CodeContractSample
{
    using System.Diagnostics.Contracts;

    internal class InvariantSample
    {
        public int TestProperty { get; set; }

        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            Contract.Invariant(TestProperty > 0);
        }
    }
}

事前条件も事後条件もメソッド呼び出しが必要だったので、自動実装プロパティ等では上のようにContractInvariantMethod属性を使います。private void ObjectInvariantMethodは固定の関数で、コントラクト専用の関数です。

 

4.契約を使う

Contractは常に実行される訳ではありません。どのような運用をしたいかに対応できるよう、Contract違反しても何も起きないこともありますし、例外をスローさせることもできます。そこでコントラクトをどう設定したいかを決める必要があります。Help Documentによると以下のようなフローのようです。

flow

Code Contractをインストールすると出てくる、Visual Studioのプロジェクトプロパティに [Code Contracts] タブにAssembly Mode があります。ここで、上記用法①or②→[Standard Contract Requires]、 ③→[Custom Parameter Validation]  に設定します。

上の用法とも関連しますが、[Code Contracts]タブのPerform Runtime Contract Checkingの設定によって各種コントラクトがどうなるか、理解しておく必要があります。

dousajoukyou

Help Documentと私の確認結果で少し違いが…??上の表は参考程度に。Debugビルドではfullに設定して、ReleaseビルドではRelease Requiresがいいところかなと思います。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中