事前条件で楽できないかなーという考え

今回の実装例はJava

なので、Javaでできる中で話を展開してみる。
ただし、絵空事はあり。

2/24 追記: あくまでDbCの一実装として例外のパターンを使っているに過ぎない。
DbCの事前条件として用語を使うのであれば、例外を投げるのが必ずしも正しいとは限らない点に注意。
・・・DbCについては時間ある時に青本読みます。

普通の実装

例えば0から100の範囲の整数で値を設定できるメソッドがあるとする。
範囲外の値を設定した場合、例外をスローする仕様である。

public void setVolume(int volume) {
    if( !( 0 <= volume && volume <= 100) ) {
        throw new IllegalArgumentException(
            "0以上100以下の値を設定してください。(値="+volume+")");
    }
    this.volume = volume;
}

こういうメソッドは多く作られる(例えば正の整数のみに対応していたり)と思うのだが、毎回このような具体的な処理を書かずにすむ方法はないのだろうか?
毎回ifとthrowを書くのも非常に面倒な作業だと思う。たとえコピペして、中身を書き換えるとしても、だ。
また、(おそらく)メソッドの行数が3行も長くなり、メソッドが煩雑な処理をしている印象を受ける。
実際にこのメソッドでやりたいことは値の設定なのだが、正直事前条件チェックの方が処理のメインを張っているように見えてしまう。

事前条件とその簡易実装

事前条件(Precondition)とは、そのメソッドが呼び出されるときの条件をチェックするものである。条件の内容は引数の値から特定のインスタンスの状態まで考えられるだろう。
そこで、例えば次のような実装をしてみた。(実際、requireメソッドはScalaでは用意されていたはず)

public final class Precondition {
    private Precondition() {}
    public static void require(boolean ... conditions) {
    for(boolean cond : conditions) {
        if( !cond ) {
            throw new IllegalArgumentException();
	}
    }
}

// 実際に使用してみる
public void setVolume(int volume) {
    Precondition.require(0 <= v, v <= 100);
    this.volume = volume;
}

一行にまとめられた上、Precondition(事前条件)/require(要求)/真偽値(必要な条件)が自明にソースコードの中に現れている。
直にif/throwを書くよりは多少はましになったように思える。
しかし、この記述方法だと以下のような欠点があるように思う。

  • [一番言いたいこと]この記述はドキュメントに反映されず、DRY原則違反が起きている。もちろん、if/throwを記述した場合も同じ。*1
  • いずれかの条件に違反したことを検出できるが、どの条件に違反したのかが分からない。(よって、例外のメッセージを適切に出せない)


この二つの問題を何とかJavaDRY原則なしに解決しようと思うと、おそらくアスペクト指向フレームワーク + アノテーションを利用することで実現可能。
Contract4Jならば、おそらくこの問題を解決してくれると思われる(試していない)*2

というわけで妄想

Eiffelとかは普通に言語機能に事前条件の記述が可能になっていたりする。つまりは、Javadocみたいなものがあればその条件を解析可能なので、DRY原則に違反しないのではないかと思うのだ。
というわけで、こんな感じで実装できればうれしいんだけれどなぁ。

// 妄想1
public void setVolume(int volume : range(0,100)) {
    this.volume = volume;
}
// ERROR: 範囲外の値が渡されました。[expect=range(0,100), value=-1]

// 妄想2 (Eiffelでやれ / むしろC#っぽいよね。)
public void setVolume(int volume) : require(0 <= volume, volume <= 100) {
    this.volume = volume;
}
// ERROR: 事前条件第一項目が満たされていません。
// (このメッセージぐらいだったら上のトイモデルでも出せる。)

最後はただの妄想なので、本気にしないようにね!
とにかく、メソッドは本質的な処理(あるいはその呼び出し)だけを書く場所にしたいなぁと思うんだけど、どうなのかなぁ。

*1:当然ドキュメントにも設定できる値の範囲を書くはず

*2:http://www.ibm.com/developerworks/jp/java/library/j-aopwork17/index.html