たぶん Eiffel がそのはしりっぽくて、 Java にも中途半端っぽいなものが導入された、 DbC (Design by Contract) ですが、 D にもサポートされています。 またも基本的なことがわかっていない方は、 K.INABA氏の解説を参照のこと。
おしながき
事前条件、事後条件の発動タイミングはわかりやすいです。 in, out節の中身をその関数に入ったり出た時に実行する。 では invariant は?
import c.stdio; class Class { int val; this() { printf("constract\n"); } ~this() { printf("destruct\n"); } void func() { val = 1; printf("func\n"); val = 2; } void inoutfunc() in { printf("in\n"); } out { printf("out\n"); } body { printf("inoutfunc\n"); } static void sfunc() { printf("sfunc\n"); } invariant() { val = 1; printf("invariant check\n"); } } int main() { printf("call constractor\n"); Class c = new Class(); printf("-----------\n"); printf("member subst\n"); c.val = 1; printf("-----------\n"); printf("call func\n"); c.func(); printf("-----------\n"); printf("call sfunc\n"); c.sfunc(); printf("-----------\n"); printf("call inoutfunc\n"); c.inoutfunc(); printf("-----------\n"); return 0; }
実行結果は
call constractor constract invariant check ----------- member subst ----------- call func invariant check func invariant check ----------- call sfunc sfunc ----------- call inoutfunc in invariant check inoutfunc invariant check out ----------- invariant check destruct
完全に予測通りの結果。上から見ていきます。
コンストラクタは終了時に invariant節を実行します。 デフォルトコンストラクタでは invariant節は実行されませんでした。
メンバへの代入時はチェックされません。 まああたり前と言えばあたり前。 メンバは必ず private にしましょう。 速度が重要でない局面では。
普通のメソッドが呼ばれる前後で実行されています。 中で値を変えたりしてもその都度チェックされるようなことはありません。 当たり前といえばあたりまえ。
static 関数では不変条件は呼ばれません。 だから class invariant じゃなくて object invariant って呼ぶべきな気がします…
あくまで条件を記述するだけなはずなので、 順序はどうでも良いことですが、 事前条件が実行されてから不変条件、 不変条件が実行されてから事後条件が実行されます。
最後に、デストラクタの前に呼ばれています。 これもデフォルトデストラクタでは呼ばれませんでした。
さて、タイミングとは関係無い話ですが… 契約の記述は assert的なものだけで、 値は普通変えるべきではないはずですが、 今回のサンプルソースにある通り、 invariant の中で val の値を変えれてしまっています。 メソッドでの this への const 指定も無いですし、 そのへんの認識が少し甘い気がします。 でもこのおかげで invariant って hook の機構として使える気がするんですよね…
まずはサンプルコード。 K.INABAさんからコードを借用しつつ… (犬であるという設定を全く生かしてませんが)
import stream; class Dog { void bark() in { stdout.writeLine('Dog::bark in'); } out { stdout.writeLine('Dog::bark out'); } body { stdout.writeLine('bow!'); } invariant { stdout.writeLine('Dog invariant'); } } class QuietDog : Dog { override void bark() in { stdout.writeLine('QDog::bark in'); } out { stdout.writeLine('QDog::bark out'); } body { stdout.writeLine('...'); } invariant { stdout.writeLine('QDog invariant'); } } int main() { Dog taro = new Dog; Dog pochi = new QuietDog; taro.bark(); stdout.writeLine('----------'); pochi.bark(); return 0; }
実行すると…
Dog::bark in Dog invariant bow! Dog invariant Dog::bark out ---------- QDog::bark in QDog invariant Dog invariant ... QDog invariant Dog invariant QDog::bark out
つまるところ、invariant は継承され、 オーバーライドする時は契約ごとオーバーライドされます。
D と違い、Eiffel では契約の中では、条件式を書いていきます。 assert の中身だけを書く感じです。 その条件式が一つでも false を返すと ただちに例外が投げられます。
Eiffel の方がわかりやすいですが、 D の方がイロイロと潰しが効く感じがしますね。 「仕様をコードで記述する」ということを追い求めた言語である Eiffel と、 現実の問題を解決することを考える言語 D のポリシーの 違いのようなものを感じます。
結構必要なものだと思うんですが、 Eiffel では old という予約語を使って こんな書き方ができます。
あとは Eiffel には loop での不変条件などもあります。 D では for 文の中を in out 節で囲めば問題無いですね。 Eiffel ってこんな感じでやたらと予約語とか増やすんですよね… このへんにもポリシーの違いが。
全てリンクフリーです。 コード片は自由に使用していただいて構いません。 その他のものはGPL扱いであればあらゆる使用に関して文句は言いません。 なにかあれば下記メールアドレスへ。