アスペクト指向プログラミング

アスペクトの作成方法

アスペクトの良い作成方法というのは, 今のところこれといった手法はありません.

設計段階(モデリングなど)で使う手法はまだ研究段階で, UML などを用いた表記法としても決まりきった(有効だと分かっている)方法 というのはないように思います.

リファクタリング,リバースエンジニアリング系の研究は活発で, 実際に書かれてしまった「分散したコード」を発見して アスペクトとしてまとめるというアプローチは複数研究されています.

いつ,何をアスペクトにするべきか?

オブジェクト指向設計では,きわめておおざっぱな指針として, 「名詞はオブジェクトの候補になる,動詞はオブジェクトの操作の候補になる」 というものがありました.

これに対して,「アスペクトは,オブジェクトに付加される形容詞なのではないか」 という指摘があったりもしますが,本当かどうかは今のところ不明です.

一般的な指針

アスペクトを作るべきかどうかを判断する材料として, 今までモジュールを作る際に使ってきた指針で有効なものがあります.

  • 同時に変更されるものは1つのモジュールにする.
    • 開発者が,1つの関心事(特定の機能,要求)について変更するたびに, 同時に複数のモジュールを変更する必要が出てくる場合があります. それらは,アスペクトの候補になりえます.
    • ただし,特定のクラス階層に収まる場合など, 他のモジュール化手法が適用できないかどうかは確認しておく必要があります.
  • 循環依存性をアスペクトとしてモジュール化する.
    • この場合,アスペクトの候補となるのは,オブジェクト間の関連です. アスペクトを作ることで,オブジェクト単体の機能と, 関連によって生じる機能とを分離しておくことができます.
    • モジュール間の循環依存は,保守性に悪影響を与えると言われています. たとえば,テスト工程において,単方向の依存関係だけで構成されるシステムでは, 使われる側のモジュールを先にテストしてから, 使う側のモジュールをテストするというように順番にテストを実施していきますが, 循環依存したモジュール群は,テストの順番を決められません.
    • 循環依存を断ち切る一つの方法は,抽象インタフェースを使って オブジェクトにアクセスする方法があります. しかし,オブジェクト間に密接な関連がある場合, 抽象インタフェースを使いにくいこともあります. また,インタフェースが増えすぎると,管理コストが上がるという問題もあります.

メソッドとアドバイスのどちらを選ぶか

以下の論文で,メソッドとアドバイスの実装それぞれに対し,色々な機能追加を行い,どのようなソースコードの変更が必要であったか,比較が行われています.

Gregor Kiczales, Mira Mezini: Separation of Concerns with
Procedures, Annotations, Advice and Pointcuts.
Proceedings of ECOOP 2005.

メソッドもアドバイスも,プログラムのある時点で,特定の処理を実行するという点では共通の存在です.手続き呼び出しは,呼び出し文の位置で,そこに書かれた手続きを実行します.一方で,アドバイスは,ポイントカットとして記述された位置の集合に対して,アドバイスとしてかかれた手続きを実行します.

ある時点で実行したい処理があるとき,それをメソッド(手続き)として実装して,呼び出し文だけを配置すると,その処理自体と,それが実行されるコンテキストのいずれもが分かりやすくなり,また,他のコンテキストで,処理を再利用できるようになります.実行したい処理自体が変更されるときには,メソッド1つを変更すればよい,ということにもなります.

行うべき処理を「いつ」実行するかというポイントが複数あるときで,以下の条件に該当する場合は,アドバイスの使用を考慮するべきです.

  • その処理を呼び出さなければならない箇所が多い.
  • 呼び出す位置と実行すべき処理との結合が,無効になったり,コンテキスト依存であったりする.
  • その処理を呼び出すやり方が変わる可能性がある.

たとえば「Shape クラスの位置変更に関わるメソッドが実行されたときにDisplay.updateが呼ばれなければならない」というルールは,Shape クラスのメソッドが複数あり,メソッドが追加された場合には影響を受けることになるため,アドバイスとして実装するほうが適している,ということになります.

ポイントカットとアノテーション

上記の論文では,また,アドバイスに対するポイントカットの使い方に関する言及も行われています.ポイントカットを指定するには,以下の方法があります.

  • メソッド名などを具体的に列挙する.
  • メソッド名などをパターンマッチで指定する.
  • 名前を付けられたポイントカット(定義自体はパターンマッチ等)を指定する.
  • アノテーションを使ってメソッドの集合などを指定する.

これに対して,以下のような指針が提案されています.

  • 1つのアドバイスが実行されるべき位置が,共通の性質を持っているなら,名前付きポイントカットか,アノテーションを使って,その性質を明らかにするべきである.
  • ポイントカットで指定した場合は性質の定義を一箇所に記述することができ,アノテーションの場合は,性質の指定自体はコードの複数の場所に配置されることになる.
  • 性質の名前としては,その時点で真であるような条件を選ぶべきで,その時点で実行するべき処理の名前などを付けない.

「図形オブジェクトの状態が更新されたなら画面を更新する」という例であれば, ポイントカットに"DisplayStateChanged" といった名前を付けて

pointcut DisplayStateChanged(): execution(* Shape.set*(*));
after(): DisplayStateChanged() { Display.update(); }

と定義したほうが,

pointcut RefreshDisplay(): execution(* Shape.set*(*));
after(): RefreshDisplay() { Display.update(); }

よりもルールが明確になっているといえます. アノテーションの名前の付け方についても,同様のことが言えます.

ポイントカットでは,上記のようなパターンマッチではなく,メソッド名を列挙する形で定義することも可能です.メソッド名を列挙すると,確実に対象のメソッドを指定することができ,パターンマッチに「偶然」マッチしてしまう可能性もありません.逆に,メソッド名の変更などに合わせて,列挙した内容を変更する必要があります.

// パターンマッチによる指定
pointcut DisplayStateChanged1(): execution(* Shape.set*(*));
// 列挙による指定
pointcut DisplayStateChanged2(): execution(void Point.setX(int)) || 
                                 execution(* Point.setY(int)) || 
                                 execution(* Line.setP1(Point)) ||
                                 execution(* Line.setP2(Point));

一長一短がありますが,列挙による定義は,次の条件がすべて成り立つ場合には良い選択肢であろうと主張されています.

  • 指定したいメソッド群に共通の安定したポイントカットを記述することが難しい.
  • 指定するべき対象が少ない.

パターンマッチによる定義は,上記の反対に,以下の条件のいずれかが成り立つ場合には良い選択肢となります.

  • 指定したいメソッド群に共通の安定したポイントカットが定義できる.
  • 指定するべき対象が(10個より)多い.

アノテーションの利用については,次の条件がすべて成り立つ場合に考慮するべきです.

  • 指定したいメソッド群に共通の安定したポイントカットを記述することが難しい.
  • アノテーションの名前自体は変化しないと予測できる.
  • アノテーションの意味するものがその位置に存在しており,ソフトウェアの構成の変化やコンテキストによって変化しない.

「安定した」というのは,ポイントカットがマッチする集合があまり変動しない,といった意味で使われています.たとえば,"Shape+.set*(*)" のような指定は,「Shapeクラスとそのサブクラス」と十分に範囲を限って,かつ「setで始まるメソッドは状態の変更である」という広く知られたJavaの命名規則に従っているため,十分に安定性があると判断できる,と主張されています.「set*(..)」のように名前がsetで始まるすべてのメソッドである,というように広い範囲を指定するポイントカットは安定性が悪い,ということになります.

アスペクト指向リファクタリング

リファクタリングというのは,可読性や保守性を向上させるために プログラムの機能を変更することなく書き換える作業です.

アスペクト指向リファクタリングは, オブジェクト指向プログラムにアスペクトを導入してコードを書き換える作業, アスペクト指向プログラムに対するリファクタリング作業の両方を含んでいます. また,よく似た Aspect-"aware" Refactoring という言葉は, アスペクトに影響を受けているかもしれないクラスに対して リファクタリングを行う作業を意味します.

アスペクト抽出の手順

以下は,Ramnivas Laddad の記事における, AspectJ によるアスペクト抽出を行う場合の手順です.

  • アスペクトの候補を見つける.
  • 空のアスペクト(今から作るアスペクト)を用意する.
  • 「いつ」動作するのか,ポイントカット記述を作成する. この時点では,元の処理をそのまま移動するために できるだけ厳密なポイントカット記述を作成します. また,この時点では処理はまだ関連付けません.
  • アスペクトが行うべき(アスペクトに移動させる)処理を, クラスが行っていたら警告を出すように 「declare warning」宣言を記述する.
    • 注: ここだけ AspectJ 固有で,Optional なステップです.
  • 実際のメソッド呼び出し文などを,アスペクトの中に移動させます.
  • 最後に,ポイントカット記述を整理します.

以下に,Ramnivas Laddad が書いている記事(英語)があります.
http://www.theserverside.com/articles/article.tss?l=AspectOrientedRefactoringPart1

アスペクトの "におい"

ここでは,アスペクトの利用を検討するタイミングとして, 次の文献で紹介されている項目を紹介します.

Miguel P. Monteiro and João M. Fernandes:
Towards a Catalogue of Refactorings and Code Smells for AspectJ.
Transactions on Aspect-Oriented Software Development I, 
LNCS 3880, pp.214-258, 2006.

多数のモジュールに変更が波及してしまう

これは「一般的な指針」でも述べていますが, 1つの変更を行おうとしたときに, 複数のモジュールを変更することになってしまうようであれば, それはアスペクトとして1箇所に閉じ込められないか, 検討してみる価値があります.

1つのクラスが,複数の役割を持っている

「1つのクラスは1つの役割を担当する」というのが理想ですが, オブジェクト指向におけるデザインパターンなどを適用しようとしたとき, 1つのクラスに複数の役割を割り当てることになる場合があります.

もし,複数のクラスが同種の役割のコードを持たなくてはならないのであれば, それは横断的関心事となり得ます (例: Observer パターンを複数箇所で使っている場合, それぞれで Observer の実装コードを用意する必要があります).

あるクラスから,特定の役割に関するコードを分離してしまいたい場合, アスペクトは有望な選択肢となります.

AspectJ であればインタータイプ宣言などを用いると, クラスの実装の一部だけをアスペクト側に容易に切り出すことができます. また,デザインパターンの抽象アスペクト (ソースコードのサンプル)を適用例として参考にしてください.

「デフォルトの振る舞い」を定義した抽象クラスが存在する

あるインタフェース(たとえば List)に対して, そのデフォルトの振る舞いを定義したクラス(AbstractList)を 提供したくなる場合があります.

しかし,Java などの Mix-in などをサポートしない言語では, 1つのクラスに複数のインタフェースを持たせたいとき, このような「デフォルトの振る舞い」を複数持たせることができません.

インタータイプ宣言を使ってデフォルトの振る舞いを アスペクト側に定義することで 中間のクラスを取り除き, インタフェースごとのデフォルトの実装を利用することができます.

アスペクトマイニング: 既存のコードベースからアスペクトを導出する

アスペクトマイニングというのは, 既存のオブジェクト指向プログラムを入力として, アスペクトとするべき候補を発見する手法です.

先の節で述べたような「におい」を探す手法を, 自動的にソースコード全体に適用することで, アスペクトの候補を効果的に発見しようと研究されています.

アプローチ

コードクローンをベースにまとめる

ある程度の長さのコード片で,類似したものが 複数個所に出現しているものをコードクローンと呼びます.

クローン検出手法およびツールについては様々な研究がなされています.

ある程度のサイズの「定型的な処理」が複数個所に渡って現れる場合, それらのコード片はクローンとして検出されます. 検出されたクローンのうち, たとえば同一クラスにあるものであれば「メソッドの抽出」, クラス階層に分散したものであれば「メソッドの引き上げ」などが適用できますが, クラス階層に関係なく分散したコードを アスペクトとして抽出できる可能性があります.

この手法の弱点は,コードクローンの性質上, ある程度まとまったサイズの処理でなければ 発見できないという点です. また,字面上同一の処理でさえあればクローンとして検出されるため, コードとしてまとめるべきかそうでないか,個別に検討する必要があります.

grep を手がかりにまとめる

特定のメソッド呼び出し(たとえばロギングなど)がアスペクトの候補だと 分かっている場合,grep などを使ってすべての呼び出し箇所を 探索する手法が有力です. ちょっとした呼び出し処理が分散している場合,クローン抽出よりも有望です.

単なる grep だけではシステム全体の様子は分かりません. 検索結果がどのモジュールにどのくらい含まれているかを閲覧するための ツールとして,Aspect Browser があります. これを使って,キーワード検索の結果が あまりに複数のモジュールに分散しているようならまとめるといったような, ほかのツールとの連携が重要です.

grep による検索は意味情報を使用しないため, たまたま字句的に一致するだけのものなども抽出する点には注意が必要です. 他の意味情報を含めた検索ツールならば(Eclipse における Java 検索など), より意味のある情報を取り出せる可能性があります.

メソッド(関数)ごとの Fan-In をベースに見つける

Fan-In とは,注目したメソッドあるいは関数を 「誰が(どこで)呼んでいるか」という情報です.

メソッドごとに,誰から呼び出されているかを列挙していき, 呼び出されている数が多いものから順番にアスペクトの候補にならないか調べていく アプローチです.

いわゆるユーティリティ(List や Arrays などの作業用クラス)を 何らかの手段で(手作業などで)除いてから探索する必要がありますが, どのクラスがアスペクトになりそうか分からない場合には有効である可能性があります.

発見された候補をまとめる方法

分散した処理を見つけてから,今度はそれらの間に どのようなルールがあるのかを見つける必要があります.

残念ながら,この手続きを自動化したツールは, 今のところ研究段階です.


トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2007-01-04 (木) 04:13:32 (4334d)