Contract4j5を使ってみた
DbC(Design by Contract)をJavaで実現できるContract4j5を使ってみました。
セットアップ
セットアップ方法が記されているページ(Getting Started with Contract4J)を参考にセットアップしました。
contract4j5.jarをライブラリに追加する
プロジェクトのビルドパスに[外部jar追加]ボタンを押して、contract4j5.jarを追加します。
AspectJビルドの設定にcontract4j5.jar他ライブラリを追加する
プロジェクトのAspectJビルドパス-[Inpath]に[外部jar追加]ボタンを押して、contract4j5.jarを追加します。また、Contract4j5_080.zipに含まれていたlibディレクトリ下のjarファイルを全てビルドパスに追加します。
ソースを作成します。
パッケージは何でも良いと思うのですがsampleとします。ソースは以下のようにコーディングしました。Contract4J5を使わず普通のJavaアプリケーションとして実行すると”30"と表示されるプログラムです。
public class Sample { public static void main(String[] args) { Person person = new Person("Taro YAMADA", 20); int future = 10; System.out.println(person.getFutureAge(future)); } }
import org.contract4j5.contract.*; /* Contract4J5を使う */ @Contract /* DbC対象のクラス */ public class Person { private String name; @Invar("age >= 0") /* 年齢は0未満になることがない */ private int age; Person() { name = new String("-"); age = -1; } Person(String aName, int aAge) { name = aName; age = aAge; } @Pre("aFuture >= 0") @Post("$return >= 0") int getFutureAge(int aFuture) { return age + aFuture; } @Post("$return != null") String getName() { return name; } }
実行してみる
普通のJavaアプリケーションとして実行すると、想定通り"30"とだけ表示されます。
次にわざとPersonクラスを間違った使い方をしてみます。main()のPersonオブジェクトを生成する際、第2引数を20ではなく-1にして実行してみます。
Person person = new Person("Taro YAMADA", -1);
すると、以下のようなエラーが表示されました。ばっちり事前条件の違反(クラスの使い方が間違っている)を検出してました。
[FATAL] DefaultContractEnforcer: *** Contract Failure (Person.java:16): Invar test "age >= 0" for "sample.Person" failed. [failure cause = null]
Exception in thread "main" org.contract4j5.errors.ContractError: *** Contract Failure (Person.java:16): Invar test "age >= 0" for "sample.Person" failed. [failure cause = null]
at org.contract4j5.enforcer.ContractEnforcerHelper.makeContractError(ContractEnforcerHelper.java:183)
at org.contract4j5.enforcer.defaultimpl.DefaultContractEnforcer.finishFailureHandling(DefaultContractEnforcer.java:29)
at org.contract4j5.enforcer.ContractEnforcerHelper.handleFailure(ContractEnforcerHelper.java:110)
at org.contract4j5.enforcer.ContractEnforcerHelper.invokeTest(ContractEnforcerHelper.java:92)
at org.contract4j5.aspects.InvariantFieldCtorConditions$InvariantFieldCtorConditionsPerCtor.ajc$afterReturning$org_contract4j5_aspects_InvariantFieldCtorConditions$InvariantFieldCtorConditionsPerCtor$2$d2878b90(InvariantFieldCtorConditions.aj:143)
at sample.Person.(Person.java:19)
at sample.Sample.main(Sample.java:9)
所感
JavaでDbCの実現方法を探していました。今回の実験でContract4J5を使うと事前条件/不変条件/事後条件がコーディングできそうなことが判り、とてもウキウキしています。
ところで、「なぜ事前条件がチェックできることがうれしいのか?」と感じられる方がいるかもしれません。その人のために”DbCを盛り込めるContract4J5のようなライブラリを使えるのは幸せなことなんだ”ということを自分なりに訴えてみます。
自分一人でプログラムを作る、小さいプログラムを作る、というケースではDbCのありがたみを感じられるものではないかもしれません。しかし、私のようにプログラミングを職業としている場合は事情が異なります。
職業でプログラミングをする場合、大勢でひとつのプログラムをコーディングします。その際、汎用的なクラス(以降部品クラスと呼びます)は共有ライブラリとして予め作成しておきます。部品クラスを作る人は、部品クラスの使用法を設計しjavadocを含むドキュメントに記します。部品クラスを使う人は、設計書やjavadocに書かれている使用法を見ながらコーディングします。
ドキュメントがきちんと書かれていれば、ドキュメント通りに部品クラスが作られていれば、ドキュメント通りに共通部品を使っていれば、Contract4J5は不要かもしれません。しかし、実際の現場ではそうならないケースが多です。よっぽどの天才集団で無い限り、開発の中では必ず先述した条件のいずれかが欠けた状態になります。
DbCをソースコードに盛り込める場合、実際にシステムを動かした後にバグを見つける手間を大幅に短縮することができます。部品クラスを利用するクラスをコーディングしている時、あるいは部品クラスの単体テスト時に判るようになります。つまり、ドキュメントに書いているとおりに共通部品が作られているか、ドキュメント通りに共通部品を使っているかということを早期に(否が応でも)確認できます。
仕事でプログラムを作成する場合は、なるべく早く/なるべく安く/目指す品質のプログラムを作る必要があり、物作りの速度というのがこれらに直結しています。このような環境下では、Contract4J5のように早期にバグ*1を見つる仕組みを用いることにより、コーディングしている人が矢早く仕事を切り上げられ、会社としても人件費が抑えられ、仕事としてうまくいく可能性を上げられるのです。
さて、JavaでDbCを実現するのはContract4J5だけではないようです。Googleからもcofojaというライブラリがリリースされるようです。私は未だcofojaを使えていませんが、こちらもなかなか良さそうです。いずれ再チャレンジしてみようと思っています。
*1:ドキュメントが間違っている。ドキュメント通りに部品クラスを使っていない。ドキュメント通りに共通クラスが作られていない、というケースです。