D 始めました。 - DbC

since: 2003-06-16 update: 2003-06-16 count: 5906

たぶん Eiffel がそのはしりっぽくて、 Java にも中途半端っぽいなものが導入された、 DbC (Design by Contract) ですが、 D にもサポートされています。 またも基本的なことがわかっていない方は、 K.INABA氏の解説を参照のこと。

おしながき

invariant のタイミング

事前条件、事後条件の発動タイミングはわかりやすいです。 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 は継承され、 オーバーライドする時は契約ごとオーバーライドされます。

Eiffel との比較

D と違い、Eiffel では契約の中では、条件式を書いていきます。 assert の中身だけを書く感じです。 その条件式が一つでも false を返すと ただちに例外が投げられます。

Eiffel の方がわかりやすいですが、 D の方がイロイロと潰しが効く感じがしますね。 「仕様をコードで記述する」ということを追い求めた言語である Eiffel と、 現実の問題を解決することを考える言語 D のポリシーの 違いのようなものを感じます。

結構必要なものだと思うんですが、 Eiffel では old という予約語を使って こんな書き方ができます。

あとは Eiffel には loop での不変条件などもあります。 D では for 文の中を in out 節で囲めば問題無いですね。 Eiffel ってこんな感じでやたらと予約語とか増やすんですよね… このへんにもポリシーの違いが。


home / index

全てリンクフリーです。 コード片は自由に使用していただいて構いません。 その他のものはGPL扱いであればあらゆる使用に関して文句は言いません。 なにかあれば下記メールアドレスへ。

shinichiro.hamaji _at_ gmail.com / shinichiro.h