アスペクト指向プログラミング オーバービューThe Rational Edge (24)

本記事でPollice氏は、アスペクト指向プログラミング(AOP)の概要を示し、AOPの将来性、その将来性を実現するために必要なもの、およびそれに伴う問題や障害について述べている。

» 2004年04月13日 12時00分 公開
[Gary Pollice(Worcester Polytechnic Institute),@IT]
ALT 本記事は、IBM developerWorksからアットマーク・アイティが許諾を得て翻訳、転載したものです。

 理想的な世界では、ソフトウェアの作り直しなどという作業は存在しないだろう。おそらく、適切なオブジェクト・モデルが最初から得られ、ウォーターフォール型の開発アプローチがうまく機能するだろう。しかし悲しいかな、われわれが住んでいるのは理想的な世界ではない。そのため、ソフトウェア・システムを構築するためのよりよい方法を常に模索しているのである。

 われわれは現実的に、すべての状況に対して適切なプロセス、手法、言語、プラットフォームなどが存在しないことを認識している。だからこそ、ツールのレパートリーを広げて、特別なツールや手法が必要になったときにすぐにそれらを利用できるように準備しているのである。この業界(もちろんIT業界だ)の歴史は、高級言語、構造化プログラミング、オブジェクト指向アプローチの導入から、スパイラル型や反復型の開発に至るまで、ソフトウェア構築アプローチの改良の歴史でもある。このラインアップに最近加わったのが、アスペクト指向プログラミング(AOP:aspect-oriented programming)である。AOPは、アスペクト指向ソフトウェア開発(AOSD:aspect-oriented software development)の1つの側面を表す。このコラムではAOPの概要を示し、AOPの将来性、その将来性を実現するために必要なもの、それに伴う問題や障害について説明する。また、少しではあるがAOSDについても説明する。

◇ AOPとは何か?

 AOPという概念は新しいものに思えるかもしれないが、実際には以前から存在していたものである。AOPに関して今日利用できる知識は多くの人々の貢献によるものであり、各人がその創始者であると主張することもできる。しかし、一般的にAOPと最も関連の深い人物はGregor Kiczales氏である。現在、彼はBritish Columbia大学でソフトウェアのモジュール性に関する研究に従事している。Kiczales氏は、1984年から1999年までXerox Palo Alto Research Center(PARC)でAOPの研究に取り組み、AOPの実装における第一人者であった。

 AOPを説明する最もよい方法は、例を示すことだろう。ここでは、あなたが大規模なソフトウェア・システムを保守する立場にあると仮定しよう。このシステムは、所属する組織の給与計算と人事の機能を管理する。ここで、従業員データに対する変更をすべてログに記録するように経営者側から要求されたとしたらどうするか。これには、減給、昇給、残業手当など、給与計算に関する変更が含まれる。また、従業員の肩書きや個人データなど、従業員のあらゆる情報に関するあらゆる変更が含まれる。この要求を満たすには、どうしたらよいだろうか。

 多くの人は、ほかによい方法が思い付かないため、コード中を調べて、適当な場所にロギング・メソッドの呼び出しを挿入するだろう。システムが適切に設計され、作成されていれば、コードに加える変更は数カ所だけで済む。しかし多くのシステムでは、多くの場所にそのような挿入が必要になる。オブジェクト指向システムであれば、ロギング・クラスを作成し、そのインスタンスを使用してロギングを行う。さまざまなファイルやデータベースを扱うために、複数の階層のクラスが必要な場合もある。このような方法で要求を満たそうとするのは容易な作業ではなく、必要な挿入が確実に行われるように保証することは困難である。

 たとえ、優れたオブジェクト指向システムの実装がなされていたとしても、いくつかの問題に直面することになるだろう。『Mastering AspectJ』の中でJoseph D. Gradecki氏とNicholas Lesiecki氏は、オブジェクト指向システムでは、変更するのが困難なクラスや再利用できないコードが作成されることが多く、システムをトレースするのが難しい(つまり、オブジェクト間のメッセージが非常に多いため、コードの論理スレッドを追うのが困難である)と指摘している(注1)。

◇ 横断的関心事(cross-cutting concerns)

 この例で要求されるロギング機能は、横断的関心事(cross-cutting concern)である。つまり、システム全体にかかわる機能であり、システムのさまざまな場所で変更をロギングしなければならないため、1つや2つの特定のクラスにロギング機能を分離(カプセル化)することはできないのである。われわれの希望は、システムを保守が容易な状態に保つことであり、そのため、コードをできるだけ簡潔に保ちながらロギングを実現したいということだ。また、システムのアーキテクチャに対して構造上の大きな変更を加えることは避けたい。そうなると、ロギングのような横断的関心事をどのように実装したらよいのだろうか。ロギングを行うクラスを作成し、適当な挿入を行うことによって、すべてのコードをリファクタリングすることは可能であるが、大規模システムでは、非常に時間がかかり、エラーの発生しやすい作業となってしまう。

 AOPは、このような横断的関心事を表現し、それらをシステムに自動的に組み込むためのメカニズムであるアスペクト(aspect)を提供することによって、横断的関心事を処理するように設計されている。AOPは、既存のプログラミングのパラダイムや言語を「置き換える」ものではなく、それらと「連携」して表現力や有用性を向上させるためのものである。AOPを利用すると、関心事の分離(separation of concerns)をより適切に表現できる。これは、保守が容易なソフトウェア・システムを適切に設計するために必要である。一部の関心事は、カプセル化されたオブジェクト、すなわちコンポーネントとして適切に表現され、それ以外の関心事は横断的関心事として適切に表現される。

◇ AspectJでのロギングの例

 引き続きロギングの問題について考え、それがAspectJによってどのように解決されるかを見てみよう。AspectJとは、Java言語を拡張したAOPの実装のことで、おそらくAOPの実装としては最も有名で、最も広く使われているものである。もし、読者がJava言語に精通していないとしても、これから説明する主要な概念については理解できるはずである。

 下記の図(従業員情報の更新に関係するクラス)は、システムの変更を実現する1つの方法を示している。Financeシステムには、従業員の会計データを更新するためのいくつかのメソッドと1つのインターフェイスがある。これらのメソッド名はすべて「update」で始まり(例えばupdateFederalTaxInfo)、各メソッドは引数としてEmployeeオブジェクトを取る。 また、従業員の人事情報は、に示されたEmployeeオブジェクトのメソッドを使って更新される。

ALT 図 従業員情報の更新に関係するクラス

 ここで何をしなければならないかを文章で記述すると、次のようになる。任意の更新メソッドを呼び出し、その更新処理が成功するたびに、ロギング・メッセージを出力する。話を簡単にするために、ロギング・メッセージを標準出力に出力する。実際のシステムでは、ログ・ファイルに出力することになるだろう。AspectJを使ってこのソリューションを実装するには、次の3つのステップが必要である。

  1. コード内にロギング・コードを挿入する場所を識別する。AspectJでは、これを「結合点の定義」と呼ぶ
  2. ロギング・コードを記述する
  3. 新しいコードをコンパイルし、システムに統合する

 各ステップについて、次の3つのセクションで詳しく説明する。

◇ 結合点の定義

 結合点(join point)とは、われわれの関心事がアプリケーションと交差する、コード内の適切に定義されたポイントのことである。一般的に、それぞれの関心事について多くの結合点が存在する。結合点が1つか2つ程度であれば、苦労せずに手作業でコードを変更できる。

 AspectJでは、結合点をポイントカット(pointcut)としてグループ化することによって、結合点を定義する(AspectJの構文は豊富であり、ここではそのすべてについては説明しない)。まず初めに、2つのポイントカットを定義する。1つはEmployeeクラス内の結合点をグループ化するためのものであり、もう1つはIEmployeeFinanceコンポーネント内の結合点をグループ化するためのものである。これらを定義するコードは次のようになる。

pointcut employeeUpdates(Employee e):
        call(public void Employee.update*Info()) && target(e);
pointcut employeeFinanceUpdates(Employee e) :
        call(public void update*Info(Employee)) && args(e);

 employeeUpdatesという名前の最初のポイントカットは、updateという文字列で始まり、Infoという文字列で終わる、引数を持たないEmployeeオブジェクトのメソッドを呼び出しているすべての結合点を表している。また、このポイントカットは、target指定子によって、Employeeクラスで定義されたメソッドを明確に指定している。2番目のポイントカットであるemployeeFinanceUpdatesは、updateで始まり、Infoで終わり、Employee型の引数を1つ持つ任意のメソッドを呼び出しているすべての結合点を表している。この2つのポイントカットは、総合すると、われわれが関心を抱いているすべての結合点を定義する。EmployeeクラスやIEmployeeFinanceコンポーネントに更新メソッドを追加した場合、同じ命名規則に従っている限り、それらのメソッドの呼び出しが自動的にポイントカットに含まれるようになる。つまり、更新メソッドを追加するたびに、いちいちロギング・コードを追加する必要がなくなるのである。

◇ ロギング・コードの記述

 ロギング処理を実装するコードは、Javaの通常のメソッドと同様であるが、アスペクトと呼ばれる新しい型として記述する。アスペクトは、特定の関心事に関連するコードをカプセル化するためのメカニズムである。従業員の変更をロギングするためのアスペクトの実装は、次のようになる。

public aspect EmployeeChangeLogger {
        pointcut employeeUpdates(Employee e) :
                call(public void Employee.update*Info()) && target(e);
        pointcut employeeFinanceUpdates(Employee e) :
                call(public void update*Info(Employee)) && args(e);
        after(Employee e) returning :
                employeeUpdates(e) || employeeFinanceUpdates(e) {
                System.out.println("\t>Employee : " + e.getName() +
                        " has had a change ");
                System.out.println("\t>Changed by " +
                        thisJoinPoint.getSignature());
                }
        }

 まず、アスペクトの構造がJavaのクラスと似ていることに注目してほしい。一般的にアスペクトは、Javaのクラスと同様に、独立したファイルに記述される。この例のように、以前に定義したポイントカットをアスペクトのコードに含めるのが一般的であるが、結合点を含んでいるコードの近くにそれらを含めることもできる。

 ポイントカットの後には、通常のJavaコードのメソッドと似たコード・セクションが続く。これは、AspectJではアドバイス(advice)と呼ばれる。アドバイスには、before、after、aroundの3種類があり、それぞれ結合点の前、結合点の後、結合点の代わりに実行される。さまざまなバリエーションを使ってアドバイスをカスタマイズすることもできる。上の例では、結合点において更新メソッドから戻った直後にロギングが行われる。もう1つ注目してほしいのは、アドバイスのヘッダ内でコロン(:)の直後に2つのポイントカットの名前を記述し、それらを論理OR(||)でつないでいることである。これにより、2つのポイントカットを組み合わせている。このようなことが簡単にできるのは、両方のポイントカットがEmployeeパラメータを持つからである。

 アドバイス内の2つのステートメントは、従業員の情報が変更されたことを、従業員名とともに出力する。変更される従業員オブジェクトがアドバイスの引数として渡されるので、出力する内容は簡単に変更できる。2番目のステートメントは、AspectJのJoinPointクラスを利用して、このアドバイスが実行されている結合点を出力する。アドバイスが実行されるときには関連する結合点が必ずあり、その結合点はthisJoinPointによって参照できる。

◇ コンパイルとテスト

 ロギング・コードが記述できたので、次にそれをコンパイルし、既存のシステムに統合する。この例では、EmployeeとEmployeeFinanceの2つのクラスを実装した。このほかに、次のようなmainメソッドを持つ簡単なテスト用クラスを用意する。

public static void main(String[] args) {
        Employee e = new Employee("Chris Smith");
        // 従業員情報を変更する処理を
        // ここに記述する。
        e.updateJobInfo();
        e.updateOfficeInfo();
        EmployeeFinance.updateFederalTaxInfo(e);
        EmployeeFinance.updateStateTaxInfo(e);
        }

 AOPの実装を使用しなければ、このコードは問題なく実行される。この例では、すべての更新メソッドの本体はprintステートメントだけを含んでいるので、この例を実行すると、次のような出力結果が得られる。

Updating job information
Updating office information
Updating federal tax information
Updating state tax information

 定義したアスペクトをシステムに組み込むために、アスペクトのソース・コードをプロジェクトに追加し、AspectJのコンパイラであるajcを使ってビルドする。コンパイラはそれぞれのアスペクトを処理して、アドバイス・コードを含んだクラス・ファイルを作成する。次に、それらのクラス・ファイル内の適切なメソッドへの呼び出しが、元のアプリケーション・コードの中に「織り込まれる」。AspectJの現在のリリースでは、この織り込み(weaving)はJavaのバイトコード・レベルで行われるため、中間的なソース・ファイルは生成されない。ただし、Javaのバイトコードをデコンパイルすることは可能である。

 筆者は開発用にEclipse環境を使用しており、コンパイラの呼び出しと(コンパイラへの)適切な引数の引き渡しはAspectJプラグインによって行われる。AspectJのコンパイラを使ってプロジェクトをコンパイルすると、次のような実行結果が得られる。

Updating job information
        >Employee : Chris Smith has had a change
        >Changed by void employee.Employee.updateJobInfo()
Updating office information
        >Employee : Chris Smith has had a change
        >Changed by void employee.Employee.updateOfficeInfo()
Updating federal tax information
        >Employee : Chris Smith has had a change
        >Changed by void employee.EmployeeFinance.updateFederalTaxInfo(Employee)
Updating state tax information
        >Employee : Chris Smith has had a change
        >Changed by void employee.EmployeeFinance.updateStateTaxInfo(Employee)

 これで、どの従業員の情報が変更されたか、またその変更がどこで行われたかが分かるようになった。もちろん、ロギング処理はこれより複雑なものであっても構わないが、基本的な手法は同じである。

◇ アスペクト指向テクノロジはあなたにとって何を意味するか?

 アスペクト指向テクノロジには、多くの潜在的なメリットがある。このテクノロジは、システム内の横断的関心事を指定し、カプセル化するための方法を提供する。これにより、システムの発展に伴ってシステムをよりよく保守することが可能になる――そして、システムが確実に発展することを確信できる。AOPを利用すると、体系化された方法を使って、既存のシステムに新しい機能を、関心事という形で追加できる。表現や構造を改良することによって、より長期間にわたってシステムを稼働させることができ、システム全体を書き直すことなく、徐々に改良を加えることができる。

 またAOPは、品質管理の専門家にとって重要なツールとなるだろう。AOP言語を使用すると、アプリケーション・コードの邪魔をせずに、コードを自動的にテストできる。このため、不用意にエラーの原因を追加してしまうことがなくなる。

 われわれは現在、AOSDの潜在能力を理解する初期段階にいる。このテクノロジが、さらなる研究と実験に見合うだけの十分な利益をもたらすことは明らかなようだ。われわれは、あとどれくらいすれば、AOP言語を日常的に使ってアプリケーションを開発するようになるのだろうか。その答えはだれに尋ねるかによって異なる。

 ここまでは、さまざまなメリットについて見てきたので、今度はAOSDに関するリスクと、AOSDがソフトウェア開発の主流となるために必要なものについて考えてみよう。

◇ 品質とリスク

 AOSDを品質の観点からとらえ、AspectJを使って少し調べてみた結果、メリットのほかに潜在的なリスクが存在することが判明した。ここでは3つの問題を取り上げる。これらは、AOSDが普及した場合に、品質に関してわれわれが直面する課題を示すものである。

 最初の問題は、AOPに対応するためにプロセスを変更しなければならないことである。ソフトウェアの欠陥を見つけるのに最も有効な手法の1つは、コードのインスペクションとレビューである。レビューの際に、プログラマのチームはコードを論評し、そのコードが要件を満たしているかどうかを判断する。オブジェクト指向プログラムでは、クラスあるいは関連するクラスの集まりをレビューし、それらについて判断を下す。つまり、コードを調べ、そのコードが予期せぬイベントを適切に処理するかどうか、論理的な欠陥がないかどうか、などを判断する。オブジェクト指向システムでは、特定の概念についてのデータと振る舞いが、それぞれのクラスの中に完全にカプセル化される。

 しかしAOPでは、クラスのコードを見ただけでクラスについて判断することはできない。そのコードが、あるアスペクトのアドバイスによって拡張されているかもしれないし、あるいはアドバイスによって完全に置き換えられているかもしれないからである。アプリケーションのコードについて判断できるようになるためには、各クラスのコードだけでなく、そのクラスの振る舞いに影響を与えるすべてのアスペクトのコードが参照できなければならない。しかし、その時点では、アスペクトはまだ記述されていないかもしれない。そうだとすると、アプリケーション・クラスのコードの振る舞いを単独で考慮する場合、どれくらい正確に理解できるというのだろうか。

 実は、AOPコードの正しさについて考える方法は、オブジェクト指向プログラミング(OOP)コードの場合と逆である。OOPでは、内側から外側へと進む。つまり、クラスについて考え、そのコンテキストを想定し、その後でクラスの正しさについて、単独およびほかのクラスとどのように相互作用するかという観点の両方から判断する。これに対してAOPでは、外側から内側へと調べ、それぞれの結合点に対する各アスペクトの影響を判断しなければならない。AOPについて正しく判断する方法を定め、開発者の助けとなる適切なツールと手法を開発することは、非常に価値のある研究領域といえる。

 (AOPの普及に関する)2番目のより現実的な問題は、テスト、特に単体テストのためのツールと手法の開発である。コードはアスペクトによって変更される可能性があるため、あるクラスについて完璧に動作した単体テストが、そのクラスがAOPシステムに統合されると、まったく異なる動作をする場合がある。例を使って、これを説明しよう。

 スタックは、ご存じのように、データ項目を後入れ先出し(LIFO:last-in first-out)の方法で追加および削除するためのデータ構造である。例えば、2、4、6という数値をスタックにプッシュし、その後、スタックを2回ポップすると、6と4がこの順序で取り出される。スタック・クラスのための単体テストを記述するのは簡単である。要件定義とコードを読み、正しく実装されていることを確認する。筆者がAspectJを使い始めたときに最初に行ったことの1つは、スタックを実装し、アスペクトを使ってその振る舞いをどのように変更できるかを考えることだった。筆者は、各項目を1ずつ増分するという簡単な変更を実装した。こうして単体テストを実行すると――スタックに2、4、6をプッシュし、上の2つの要素をポップして、それらが6と4であることを確認する――結果は失敗に終わる。単体テストにおいて、テストされているコードは変わっていないが、その振る舞いが変更されたのである。結果は、6と4の代わりに7と5が取り出される。

 これはささいな例であり、現実の状況では起こらないことかもしれない。しかし、この例は、悪意のあるプログラマが極めて容易に多くの被害を与えられることを示している。悪意のあるプログラマは除くとしても、変更に伴って予期せぬ副作用が生じるため、多くの欠陥が発生する可能性がある。極めて妥当な理由のために実装したアスペクトが、既存のプログラム機能に対して悪影響を及ぼさないことを保証するのは、大変困難である。

 3番目の問題は、テスト・プロセスそのものである。ツールと手法が開発されたとして、それらを有効に使って開発全体の目標を達成するには、テスト・プロセスをどのように修正したらよいのだろうか。これは大きな問題ではないかもしれないが、アスペクトを使って作成したソフトウェアをテストするためには、解決しておかなければならない問題である。

◇ AOSDの普及に関するそのほかの障壁

 AOSD法を導入する場合の最も大きな妨げとなるのは品質の問題であるが、それだけが唯一の障壁ではない。AOSDは新しいパラダイムである。ほかのパラダイム(例えば、オブジェクト指向ソフト開発)も初めはそうだったように、学習曲線が関係するため、広く普及するようになるには時間がかかるだろう。まず初めに基本的な手法とメカニズムを学習し、次に高度な手法を学習し、その後で、それらの手法を適用するための最善の方法と、それらがどのような場合に適切であるかを学習しなければならない。

 業界でのAOPの普及には、ツールが大きな役割を果たすだろう。コンパイラやエディタのほかに、さまざまなツールが必要である。例えば、システムについて判断したり、潜在的な横断的関心事を識別したり、アスペクトが存在する状態でテストを行ったりするために役立つツールが必要である。アスペクトをUMLで表現するなど、システムをより適切に表現する方法が開発されたら、ツールもそれらの方法をサポートするように進化しなければならない。

 この記事ではAspectJについて説明したが、AspectJは唯一のAOPの実装というわけではないし、Java用の唯一の実装というわけでもない。また、最近では、AOPと同様のそのほかのパラダイムも現れてきている。例えば、IBMの研究所(IBM Research)では関心事の多次元分離(multi-dimensional separation of concerns)というパラダイムが開発されており、その実装がHyperJというツールとして入手可能である(http://www.alphaworks.ibm.com/tech/hyperj)。使用する言語に対する標準的な実装が確立されるまでは、新しいパラダイムの使用にはリスクが伴う。AspectJは、進化の途上にあるAOPの実装(言語)である。横断的関心事を含むソフトウェアを開発したが、開発で使用した実装アプローチがサポートされなくなってしまう、あるいは大きく変更されてしまうといった危険は、十分に考えられる。

◇ ソフトウェア構築のより良い方法に向かって

 確かに、AOPを日常的に利用できるようにするためのツールやプロセスの開発には、しばらく時間がかかるだろう。しかし、この記事で述べた問題が解決できないものだとは思わない。AOPのための、実績のあるツールやプロセスが開発できたときに、今日よりも優れたソフトウェア構築の方法が得られると信じている。

 アジャイル・プロセス(注2)についてBarry Boehm氏が述べているように、AOPにアプローチするには注意を払う必要がある。AOPを早々と導入するにしても、AOPが主流になるまで待つにしても、ソフトウェアに対する投資が、今日および将来において確実に満足できる結果をもたらすようにしなければならない。それが正常なビジネス・センスである。

[筆者について]

Gary Pollice

 Gary Pollice氏は、マサチューセッツ州、WorcesterのWorcester Polytechnic Instituteの教授である。彼はソフトウェア工学、設計、テスト、そのほかのコンピュータ・サイエンスを教えており、学生たちのプロジェクトも指導している。学問の世界に入る前は、ビジネス・アプリケーションからコンパイラやツールに至るまで、さまざまな種類のソフトウェア開発を35年以上行ってきた。IT業界での彼の最後の仕事はIBM Rational Softwareとの仕事で、彼は「RUPの皮肉屋」と呼ばれていた。彼は、Rational Suiteの最初の開発チームのメンバーであり、Addison-Wesley社から2004年に出版された『Software Development for Small Teams:a RUP-Centric Approach』の主要な著者でもある。また、数学の学士号とコンピュータ・サイエンスの修士号を有している。



[参考文献]
 AOSDやAOPに興味を持ったら、次を参照してほしい。

▼『Communications of the ACM』(2001年10月)には、AOSDに関する記事が多く掲載されている。

▼Nicholas Lesiecki氏による“Improve Modularity with Aspect-oriented Programming”には、分かりやすい概要が示されている。http://www-106.ibm.com/developerworks/java/library/j-aspectj/

▼Nicholas Lesiecki氏による“Test flexibility with AspectJ and mock objects”には、単体テストに役立つAOPの使用方法が示されている。http://www-106.ibm.com/developerworks/java/library/j-aspectj2/?open&l=007,t=gr

▼Joseph D. Gradecki氏およびNicholas Lesiecki氏による『Mastering AspectJ』は、John Wiley社から2003年に出版された。

参考文献のより詳しいリストについては、筆者のWebページにアクセスしてほしい。http://www.cs.wpi.edu/~gpollice/Interests/AOSD.html

[注1]
『Mastering AspectJ』(Joseph D. GradeckiおよびNicholas Lesiecki著、John Wiley社、2003年)のp.8を参照。

[注2]
“Get Ready for Agile Methods, with Care”(Barry Boehm、IEEE Computer、2002年1月)

本記事は「The Rational Edge」に掲載された「A look at aspect-oriented programming」をアットマーク・アイティが翻訳したものです。

「The Rational Edge」バックナンバー

Copyright © ITmedia, Inc. All Rights Reserved.

注目のテーマ