Design by Contract(DBC) とは、ソフトウェアの品質、信頼性、そして再利用性を確証することを目的とするオブジェクト指向のソフトウェア設計のテクニックです。DBCの重要な鍵を握る概念は、下記の方法でその目的を達成できることです。
- 可能な限りコンポーネント間の通信を正確に指定する
- 通信プロセスの予測される結果と相互の義務を定義する
これらの相互の義務はコントラクト(contracts)と呼ばれ、アプリケーションがコントラクトに準拠するかをチェックするのにアサーション(assertions)を使います。簡単に言えば、アサーションはプログラムの実行のとある時点で挿入されるtrue でなくてはならないブール表記です。アサーションの失敗はソフトウェアのバグによく見られる症状ですので、それはユーザーに報告されなくてはなりません。
外部コンポーネントかライブラリーを取り扱い、(アプリケーションがそれらに受け渡しそれらから受け取る)データが正確であることを保証するときに、DBCは特に貴重です。この記事は、DBCを実装するためにAOPを採用する抽象インフラストラクチャー、それから外部コンポーネントとのコントラクトを確立するサンプル・アプリケーションを紹介します。
DBCは3種類のアサーションを識別します。
- 事前条件: 外部コンポーネントを正しく呼び出すためにクライアントが満たさなくてはならない義務。
- 事後条件: 外部コンポーネントの実行後に予測される結果。
- インバリアント: 外部コンポーネントの実行後も不変であり続ける条件。
Java言語は昔からアサーションへのネイティブのサポートを供給していたわけではありません。assertの記述はバージョン1.4に追加されています。しかしながら、常日頃から見かけるようなコーディングでDBCを応用するのは大変です。実を言えば、最も一般的な(アプリケーション・コードに事前/事後条件アサーションを直接使用する)アプローチはコードのモジュール性そして再利用性の面で深刻な障害を抱えます。このアプローチはもつれたコードの鮮明な例です。それはアサーションが必要とする非機能的なコードにビジネス・ロジックのコードを混合します。アプリケーション・コードを変更せずしてアサーションを変えたり取り除いたり出来ませんので、そのコードは柔軟性に欠けます。
この問題の理想的な解決法は以下の4つの必須条件を満たします。
- 透過性: 事前条件と事後条件のコードがビジネス・ロジックと混同していない。
- 再利用性: 解決法の大半の部分は再利用可能。
- 柔軟性: アサーションモジュールを簡単なやり方で追加、削除、そして修正可能。
- 単一性: 単純な構文を使ってアサーションを指定可能。
もしも懸念からの離脱、透過性、そして柔軟性に狙いを定めているのでしたら、多くの場合AOP(aspect-oriented programming)が正しい解決策をもたらします。(アプリケーションの多岐に渡るモジュールにたびたび含まれ、ある意味ではアプリケーションのコードと混合される、普通に使われる機能である)事前条件、事後条件、そしてインバリアントはクロスカッティングする懸念です。別々のモジュールにこれらの機能をコーディングしそれらを柔軟性に富み宣言する方法で応用することを開発者に提供するのが、AOPの目標です。
この記事は読者がAspectJ のもとにあるAOPに総合的に精通していることを想定し、AOPの入門書を目指すわけではありません。このトピックの入門記事のリストは、参考文献の章にあります。
図1にて図解されているとおり、前述の4つの必須条件を満たす解決法は3つの部分から成ります。
- (DBCとは関係の無いエレメントを含む)アプリケーション・コード
- (事前条件、事後条件、そしてインバリアントのチェック付きの)コントラクト実装
- コードとコントラクトの間の橋渡しを演出し、正しいロジックとともにコードの正しい部分にコントラクトをほどこすオブジェクト
図1. DBCの正しくモジュラー化された解決法
コントラクト実装またはアプリケーション・コードの変更無しでコントラクトの応用そして除去を可能にする柔軟性の高い解決法を、図1にて図解される仕組みは保証します。そして、それはアプリケーションに対する完全な透過性を保ちます。
コントラクトは特定のインターフェースを実装するJavaクラスであり、「bridge(ブリッジ)」はAspectJのアスペクトです。このアスペクトはコントラクトが応用される的確なポイントそしてコントラクトを応用するのに必要な制御ロジックを指定します(図2参照)。
図2. アクティビティー図として表現されるコントラクト・ロジックによる設計
図2にて図解されるロジックは全てのコントラクトに共通しますので、それを指定する共通の抽象的なアスペクトを作成できます。クラスそしてアスペクトのダイアグラムを図3にて示します。
図3. コントラクトをチェックするシステムの基本的なコンポーネント
図2のアクティビティー図にて宣言される制御ロジックを、AbstractContract アスペクトは指定します。(ContractManagerインターフェースの実装に定義される)コントラクトが応用されるプログラムの実行のとある部分を、それは表現しません(つまり、抽象的なままです)。AbstractContractアスペクトを拡張するConcreteContractアスペクトは、下記の項目を示す責任を抱きます。
-
TargetPointcutのポイントカットを介するコントラクト・チェックの実行の的確なポイント -
getContractManager()メソッドを介するコントラクト・チェックに責任を持つクラス
アプリケーションと外部モジュールの間にあるコントラクトをチェックするうえで必要なコードを含むクラスは、ContractManager インターフェースを実装します。リスト1に示されるContractManagerは、コントラクトをチェックするクラスの基本的な振る舞いを定義する単純なJavaインターフェースです。
リスト1. ContractManager インターフェース
public interface ContractManager
{
/**
* Check the preconditions
*/
public void checkPreConditions(Object thisObject, Object[] args)
throws ContractBrokeException;
/**
* Check the postconditions
*/
public void checkPostConditions(Object thisObject, Object returnValue, Object[] args)
throws ContractBrokeException;
/**
* Check the invariants
*/
public void checkInvariants(Object thisObject) throws ContractBrokeException;
}
|
チェックを入れたいそれぞれの種類のアサーションへの異なるメソッドをContractManagerインターフェースは定義します。それぞれのメソッドは、thisObject パラメーターを介す形で、(コントラクトが確証される予定の機能を呼び出す)Javaオブジェクトへのアクセスを抱きます。事前条件と事後条件のメソッドも機能の引き数(args)として受け渡される値を確認することができます。事後条件のメソッドのみがreturnValueパラメーターを介して(結果として得られた)戻り値を受け取ります。これら3つのメソッドを組み合わせて使用することにより、ほとんど全ての共通の条件をチェックできます。
AbstractContract アスペクトはコントラクトのチェックに必要な制御ロジックを実行します。このロジックはaround():targetPointcut()のアドバイスと言う形で表現されます。リスト2はAbstractContractのアスペクトを示します。
リスト2. AbstractContractのアスペクト
public abstract aspect AbstractContract
{
/**
* Define the pointcut to apply the contract checking
* MUST CONTAIN A METHOD CALL
*/
public abstract pointcut targetPointcut();
/**
* Define the ContractManager interface implementor to be used
*/
public abstract ContractManager getContractManager();
/**
* Perform the logic necessary to perform contract checking
*/
Object around(): targetPointcut()
{
ContractManager cManager = getContractManager();
System.out.println("Checking contract using:" +
cManager.getClass().getName());
if (cManager!=null)
{
System.out.println("Performing initial invariants check");
cManager.checkInvariants(thisJoinPoint.getTarget());
}
if (cManager!=null)
{
System.out.println("Performing pre-conditions check");
cManager.checkPreConditions(thisJoinPoint.getTarget(), thisJoinPoint.getArgs());
}
Object obj = proceed();
if (cManager!=null)
{
System.out.println("Performing post conditions check");
cManager.checkPostConditions(thisJoinPoint.getTarget(),
obj, thisJoinPoint.getArgs());
}
if (cManager!=null)
{
System.out.println("Performing final invariants check");
cManager.checkInvariants(thisJoinPoint.getTarget());
}
return obj;
}
}
|
具体的なコントラクト・チェッカーのアスペクトを実装するときにオーバーライドされるべき2つの抽象メソッドを、AbstractContract アスペクトはもたらします。
-
public abstract pointcut targetPointcut()は、アドバイスが適用されるべきポイントカットを表記します。ポイントカットはメソッド呼び出しでなくてはなりません。 -
public abstract ContractManager getContractManager()は、正確なコントラクト・チェックを実装するContractManagerのインスタンスを戻さなくてはなりません。
インバリアントなチェックが二度(両方ともサービス実行の前後にて)実行されていることに注目するのが重要です。サービスの実行が一部の外部フィールドの値に影響を及ぼさないことのチェックを可能にします。
コントラクトの失敗は、アドバイスの実行を停止するContractBrokeExceptionを引き起こします。
AOPでDBCを実装するために必要なインフラストラクチャーを理解するに到りましたので、それを実際に実行する段階に入ったと言えるでしょう。顧客データを回収するために外部のCRM(Customer Relationship Management - 顧客関係管理)システムを照会する必要があるとします。CRMシステムの呼び出しは、以下のとおりになる可能性があります。
Customer cus = companyCustomerSystem.getCustomer("Pluto");
開発者の観点から見れば、getCustomer は外部コンポーネントですので、getCustomer機能の実装は無意味です。しかし、それが不正な結果を戻さないことをチェックするのは非常に重要です。アプリケーションがCRMシステムへ間違っているかまたは無意味な入力を受け渡さないことを確証するのも同様に重要です。AbstractContractを拡張する具体的なアスペクトを作り上げることにより、どちらの偶発事態にも対処できます。具体的なアスペクトは2つのメソッドをオーバーライドします。
-
targetPointcut(): ポイントカットを定義し、コントラクト・チェックを応用する場所を指示する -
getContractManager(): 全てのチェックの実行に対する責任を持つContractManager実装を定義する
リスト3は、サンプル・アプリケーションの具体的なアスペクトを示します。
リスト3. 具体的なコントラクトのアスペクト
public aspect CcCompanySystem extends AbstractContract
{
public pointcut targetPointcut(): call(Customer CompanySystem.getCustomer(String));
public ContractManager getContractManager()
{
return new CompanySystemContractManager();
}
}
|
CompanySystemクラスのgetCustomerメソッドの呼び出しに表記されるポイントカットへCompanySystemContractManager と呼ばれるコントラクト・チェッカーが呼び出されることを、CcCompanySystem アスペクトは指定します。コントラクト・チェックの操作の制御ロジックを定義する必要はありません。なぜならば、それは上位の抽象的なアスペクトAbstractContract(リスト2)から継承されているからです。
最後のステップは、コントラクト・チェックをさせるためにJavaクラスを作成することです。以前に述べたとおり、このクラスはContractManagerインターフェースを実装しなくてはなりません。リスト4はサンプル・クラスCompanySystemContractManagerを表示します。
リスト4. サンプル・アプリケーション用のContractManager実装
public class CompanySystemContractManager implements ContractManager
{
/**
* Check preconditions
*/
public void checkPreConditions(Object thisObject, Object[] args)
throws ContractBrokeException
{
Object arg = args[0];
if (arg == null)
{
throw new ContractBrokeException("PRECONDITION ERROR: " +
" Argument of getCustomer shouldn't be null");
}
}
/**
* Check postconditions
*/
public void checkPostConditions(Object thisObject, Object value, Object[] args)
throws ContractBrokeException
{
if (value == null)
{
throw new ContractBrokeException("POSTCONDITION ERROR: " +
" Return value of getCustomer shouldn't be null");
}
}
/**
* Check invariants
*/
public void checkInvariants(Object thisObject) throws ContractBrokeException
{
//invariants check
}
}
|
引き数か戻り値がnullの場合のみにおいて、リスト4のCompanySystemContractManagerクラスはチェックしますが、極端に洗練されたチェックを追加するためにそれを拡張することも可能です。
注意すべき重要なポイント:全てのコントラクト・チェックは1つのCompanySystemContractManagerオブジェクトをインスタンス化しますので、最初のインバリアント・チェックの途中で専用フィールドにデータを保管し、CRMシステム呼び出しの実行後にそれらが変更されていないことを検査することにより、インバリアントをチェックできます。
アプリケーションとCRMシステムの間で簡単なコントラクトを確立させられるのは、非常に喜ばしいことです。AspectJコンパイラーを使用してアプリケーションをコンパイルした後、コントラクトはCompanySystemクラスのgetCustomerメソッドの全ての呼び出しに応用され、それとのアプリケーションの相互作用の整合性をチェックします。さらに、CompanySystemContractManager が十分に汎用化されていれば、再利用が可能です。別のコントラクト・チェックに応用するには、targetPointcut のみを再定義すればよいだけです。
サンプルの解決法は、この記事の冒頭でリストした条件を完全に満たします。
- ビジネス・ロジックのコードはコントラクト・チェックへの参照を含みませんので透過性があります。
- それは簡単なインフラストラクチャー(インターフェースと抽象的なアスペクト)に頼り、複数の状況で単独の
ContractManagerの再利用を促しますので再利用性を持ちます。 - どのアスペクトを応用しそしてつまりどのコントラクトをチェックしたいかを選ぶためにAspectJ のコンパイラー機能を使用できますので柔軟性に富みます。
- いくつかのクラスのみから成り立っていますので単純です。
この記事ではAspectJ とAOP のテクニックを活用してJavaアプリケーション開発にDBCを採用する可能なアプローチを説明しました。提案される解決策はすっきりとしていて柔軟な解決法を保証します。なぜならば、(ビジネス・ロジックから別のところでコントラクトをコーディングし宣言する形でそれらを応用させる)極端に単純でうまくモジュラー化された設計に、解決策が信頼を寄せるからです。
- Bertrand Meyer著の「Object Oriented Software Construction」は、オブジェクト指向技術とDBC(Design by Contract)への良質の入門を提供します。
- 「Design by contract; a conversation with Bertrand Meyer」で、DBC(Design by Contract)についてより多くを学びましょう。
- 「Implement Design by Contract for Java using dynamic proxies」は、JavaコーディングでDBCを実装するうえでの別のアプローチを提供します。
- 公式のAspectJ Webサイトから、AspectJとそれに関連するツールをダウンロードできます。
- Eclipse IDEは、AspectJ plug-inをフィーチャーします。
- 「アスペクト指向プログラミングで、モジュール性を改善する」(developerWorks、2002年01月)で、AOPとAspectJへの深く掘り下げた入門を入手しましょう。
- 「AspectJおよび疑似オブジェクトによる柔軟なテスト」(developerWorks、2002年05月)で、JavaをベースにしたAOPの最も有名な独自の味わいを学びましょう。
- 「AOPが密結合の憂うつさを取り除く」(developerWorks、2004年02月)は、静的クロスカッティングのテクニックへの実践的な入門です。
- 「レガシーJavaアプリケーションの保守にAOPを使用する」(developerWorks、2004年03月)は、複雑なレガシー・システムを理解し維持するアスペクト指向プログラミングをどのようにして使用するかを説明します。
- 数百以上もの数にのぼるJava技術のリソースを探したければ、developerWorksJava technologyのページでどうぞ。
-
Javaに関連する何百ものタイトルを含む技術本の包括的なリストのあるDeveloper Bookstoreを訪れてみて下さい。
- Javaに焦点を合わせたdeveloperWorksからのフリー・チュートリアルの完全なリストを、 Java technology ゾーンのTutorialsページでご覧になってください。

Filippo DiotaleviはミラノにあるIBM ItalyのITスペシャリスト(IT専門家)であり、そこでは主にJ2EEアプリケーション開発者として勤務します。彼が関心を抱く分野は、パターン、アスペクト指向プログラミング、そしてアジャイル方法論です。IBM Redbook「Patterns: Direct Connections for Intra- and Inter-enterprise」の共著者、Webサイトそして雑誌で発行された数多くの技術記事の著者、そしてJava User Group Milanoの創立者でもあります。