{t-isio, kamiya, kusumoto, inoue}@ist.osaka-u.ac.jp Assertion with Aspect 石尾 隆†,神谷 年洋‡, 楠本 真二† ,井上 克郎† †大阪大学 大学院情報科学研究科 ‡ 科学技術振興機構 さきがけ {t-isio, kamiya, kusumoto, inoue}@ist.osaka-u.ac.jp
論文の出典 Software-engineering Properties of Languages for Aspect-Technologies (SPLAT) AOSD2004併設. Lancaster, UK, March 2004. ワークショップのテーマ: ソフトウェアの品質とアスペクト指向技術の関係 アスペクト指向技術の導入で 何が良くなり,何が悪くなるのか 今回紹介させていただくこの論文の出典は,Software-Engineering Properties of Languages for Aspect-Technologies, SPLAT ワークショップです. このワークショップは,アスペクト指向ソフトウェア開発に関する国際会議であるAOSDに併設で年1回開催されているものです. ワークショップのテーマは,ソフトウェアの品質とアスペクト指向がどう関わるか,アスペクト指向技術の導入で何が良くなって何が悪くなるのか,というものです.
議論する品質 Comprehensibility Predictability Evolvability 設計,プログラムの目的(要求との対応など)の理解 Predictability プログラムがどのように振舞うかの理解 Evolvability 将来の変更に対して適切に対応できるか Semantic Interactions 導入しようとしている技術が,既存の技術とどのように相互作用するか 2004年のワークショップでは,ここに挙げたような品質についての議論を行いました.ここに品質の名前と,その簡単な説明が書いてますが,これらは,厳密に定義したものではありません.また,これらの要素は互いに独立しているわけではなく,相互に重なる部分を持っています. さて,4つの品質ですが,まず,Comprehensibility はその名の通り,設計やプログラムが理解しやすいかどうかです.Predictability は,与えられたソースコードなどから,開発者がその動作を予測できるかどうかです.Comprehensibility がプログラムや設計がどういう要求を実現するものか,という目的の理解を含むのに対して,Predictability は,プログラムのそれぞれの部分がどのように動作するのか,という理解だと考えています. Evolvability は,ソフトウェアの発展のしやすさということで,ソフトウェアが将来の変更に対して適切に対応できるかどうかです.Semantic Interactions は,導入しようとしている技術が,既存の技術とどのように相互作用を起こして,良い効果や,あるいは複雑な問題を起こすのか,という話です.
論文の概要 表明(アサーション)をアスペクトとして記述したらどうなるか 表明がもたらす効果 表明をアスペクトとして記述する効果 論文は言語要素の提案を含むが,本発表では省略 表明がもたらす効果 プログラム理解のための情報提供 安全性の向上 表明をアスペクトとして記述する効果 既存の表明で記述しにくい性質の記述が可能となる 追加・削除・変更が容易となる そして,この論文の概要ですが,表明,アサーションをアスペクト指向プログラミングを使って書いたらどうなるだろうか,というのが主題です.論文側では,そのための言語要素の提案まで含んでいますが,今回は,どんな表明をアスペクトで書いて,それで何がうれしいのか,ということをメインにお話します. 表明というのは,プログラム理解のための情報提供と安全性の向上という効果があること,表明をアスペクトとしてモジュール化することで,既存の表明では記述しにくい性質の記述が可能になり,また追加や削除,変更が容易となる,と考えています.
発表の流れ 論文の出典 表明とは 「普通の表明」の品質への影響 表明をアスペクトとして記述する アスペクト化による品質への影響 関連研究の紹介 Moxa,表明記述言語JMLへのアスペクトの導入 まとめ ここからの発表の流れですが,まず,表明とはどんなものか,ということを説明し,表明がどのように品質に影響するかを述べます.続いて,表明をアスペクトとして記述するとどうなるか,という記述例を紹介します.そして,その結果が品質へどのように影響するか述べた後,関連研究として Moxa という表明記述言語JMLへのアスペクト指向の導入を紹介させていただいて,最後にまとめという形で進みます.
表明 (assertion) とは 特定の言語要素が「何をするのか」 実現方法とは無関係 対応する実行時点ごとに異なる呼び名 関数,メソッド,プログラム文などに対して記述 その言語要素が実行される直前(または実行直後)に 成立していなくてはならない条件 実現方法とは無関係 例: 「sort 関数の終了時,配列の中身は昇順で整列」 「この関数は quick sort を使う」とは書かない 対応する実行時点ごとに異なる呼び名 メソッド実行の前後: 事前条件,事後条件 すべてのメソッドの実行前後: クラス不変条件 ループ文実行中: ループ不変条件 それでは,表明とは何かということについて説明します.表明とは,関数あるいはメソッド,プログラム文など,ある言語要素に対して,それが何をするのかを記述したものです.一般的には,その言語要素の実行直前あるいは実行直後に成立しているべき条件を,プログラム中に記述するという形になります. 表明は,何をするか,にだけ注目しており,実現方法については言及しません.たとえば,sort 関数に対して,表明として「この関数の終了時には,配列の中身は整列している」と記述することはあっても,「この関数は quick sort で配列の中身を整列させる」といった具体的な実現方法について記述することはありません.そのため,手続きやメソッドなどの機能を抽象的に表現している,といえます. 表明は,対応する言語要素に応じて,異なる呼び名が付いています.たとえば,メソッド実行の前後で成立している条件を記述する表明を事前条件,事後条件と呼んだり,すべてのメソッドの前後において成立する条件をクラス不変条件と呼んだりします.その他にも,ループの実行中,通常はその先頭で成り立っている条件を記述するループ不変条件などがあります.
表明の用途 早期の故障検出 責任の分担 表明の違反=プログラムが想定外の状態である 障害が他の場所に波及する前に検出する 障害範囲の切り分けによる原因の早期解明 責任の分担 ある一定範囲を表明で区切って責任を分担 各時点でどこまで処理が進んでいるべきか明確化 曖昧さのない分担が可能 契約による設計 手続きの事前条件を満たすのは利用側の責任 手続きの事後条件を満たすのは手続き側の責任 表明の第一の用途は,早期の故障検出です.表明に記述された条件に違反している,ということは,プログラムが想定外の状態となっていることを示しています.その状態のままプログラムの実行を続けることで影響が他の場所へ波及する前に,故障として検出することで,原因の早期解明にもつながります. また,その他の用途としては,責任の分担があります.プログラムの一定範囲を表明で区切ると,各時点でどこまで処理が進んでいるべきか明確にできるので,曖昧さのない分担が可能となります.契約による設計では,手続きの事前条件を成立させるのが利用側の責任,その事前条件をもとに事後条件を満たすのが手続き側の責任,というように分担を行います.
利用可能な記述手段 表明文 (assert 文) 事前条件,事後条件,クラス不変条件 条件検査を行うプログラム文 条件式 = true なら何もしない, false ならプログラム停止 Java や C++ など,一般的な言語で利用可能 事前条件,事後条件,クラス不変条件 Eiffel では言語としてサポート Java,C++ では JML, Larch などの支援ツール 表明は,様々なプログラミング言語で利用可能となっています.たとえば C言語やJavaでは,assert文という,条件の検査を行う文が利用できます.違反した場合は,その時点で,プログラムが停止されます. また,事前条件,事後条件,クラス不変条件などの記述は,Eiffel ではプログラミング言語のレベルで,またJavaやC++では支援ツールとしてサポートされています.
表明の品質への影響 Predictability の向上 Evolvability は悪化する可能性もあり その言語要素が何をしているかを理解する手がかり 記述した性質に抵触するようなプログラムの誤りを検出 Evolvability は悪化する可能性もあり 詳細すぎる表明が,コンポーネントの再利用を阻害する 設計変更に対して,表明の修正が必要な場合が出てくる リファクタリング時のバグ混入の防止には役立つ Comprehensibility, Semantic Interactions には特に影響しない 仕組みとして単純で,問題となるような動作を起こすことはない 表明の効果として,まず,Predictability の向上があります.表明は,その言語要素が何をしているのか理解する手がかりを提供しており,また,その記述した内容に反するようなプログラムの誤りを検出する点で,単なるコメントよりも保証が付いています. 一方で,Evolvability は,悪くなることもあります.詳細すぎる表明が書かれたメソッドやコンポーネントが,他の場面で再利用できなくなる可能性が高くなってきます.また,設計変更に対して,表明の修正が必要な場合も出てくるので,ソフトウェアの改変時に,不利になることが考えられます.しかし,機能が変わらず,単に実装をより理解しやすい形に変更するといった作業の場合は,バグ混入を避ける道具となるので,一概に悪くなるとは言えないというところでもあります. 残るComprehensibilityとSemantic Interactions については,特に大きな影響はないだろうと考えられます.これは,表明が,仕組みとして単純で,また,元のプログラムの動作を改変しないためです.
アスペクトとして書くべき表明とは (1) コンテキストに依存した表明 (2) 複数のモジュールに分散してしまう表明 コンテキスト = コンポーネント(クラスなど)が利用される場面 コンポーネントの利用者側に依存した条件 例: 長さ1以上の文字列しか格納しない List オブジェクト 特定のテストケースに対する強い制約の記述 (2) 複数のモジュールに分散してしまう表明 「関連」に対する制約 例: Observer パターンの特殊形 「Observer と Subject とは,1対1対応しなければならない (1つの Subject に複数の Observer があってはならず, 1つの Observer に複数の Subject があってはならない) 」 ここから,表明をアスペクトとして書いてみる,という話をしたいと思います.立場として,何でもアスペクトに書いてしまうというわけではなく,アスペクトで書いたほうがうれしいであろうと思われる表明だけを相手にします. そこで,ここでは2つの分類,コンテキストに依存した表明と,複数のモジュールに分散してしまう表明というのを紹介したいと思います. コンテキストに依存した表明というのは,あるコンポーネントが1つ与えられたときに,それを使う側に応じて,あるいはテスト中などの状況に応じて,カスタマイズしたい表明のことです.今回は例として,汎用的なリストに格納するものを長さ1以上の文字列に限定する,というケースについて説明します. また,もう1つの分類として,複数のモジュールに分散してしまう表明というのを考えます.こちらは,例として,Observer デザインパターンの特殊形として,Observer とSubject という,状態の変化を監視する側とされる側のオブジェクト間の関連に対して, Observer と Subject が1対1で対応しなければならない,という制約を付加することを考えます. それでは,ここから,この2つの例を順番に見ていくことにします.
(1) コンテキストに依存した表明 コンポーネント利用者に合わせた表明のカスタマイズ A(持っているもの): List of Object B(使いたいもの): List of 長さ1以上の String A と B を継承関係で作成するのは設計上問題 A と B は Subtype 関係にない 型の指定だけなら,総称型(Generics)で対応する問題 A を内部的に使う B を実装するのは手間 A に適用できる操作が B には適用できなくなる 表明で,“少し安全に” A を使う それでは,まず,コンテキストに依存した表明として,コンポーネント利用者に合わせた表明のカスタマイズについて説明します. ここでは,既に持っているコンポーネントAとして,任意のオブジェクトを格納できる List クラスを想定します.これに対して,開発者が,長さ1以上の文字列を格納するためにListを使いたいと考えたとして,これをBとします.ここで,Aを継承してBを作るというのは問題です.Bは,入力と出力がともにAより厳しい条件を背負うため,AとB は Subtype の関係とならないためです. 単なる型の指定だけなら,いわゆる総称型,generics を使った対応が考えられますが,格納したいオブジェクトの属性にまで言及していることから,そのまま使ったら解決というわけにはいきません.本当に安全な利用をしようとすると,A を部品として使って,B を作ることになりますが,そうすると,A に適用可能な操作が,B にはまったく適用できなくなるという問題が出てきます. そこで,A に対して追加の表明を少し増やして,安全性の向上をはかります.
AspectJ での表明記述の一部 適用対象 = UserClass.stringList フィールド UserClass からの stringList.add の呼び出しで, 文字列かどうか,その長さもチェック UserClass 以外からアクセスされた場合への対応は別途必要 before (UserClass user, List list, Object added): call(* List.add(..)) && this(user) && target(list) && if(list == user.stringList) && args(added) { assert (added instanceof String) && (((String)added).length() > 0); } AspectJで表明を記述してみた例を示します. ここでは,UserClass という名前のクラスが,stringList というList型のフィールドを持っていたとしています.そして,UserClass のインスタンスが List.add メソッドを呼び出したとき,文字列の長さをチェックするという形で実装しています.この記述は完全なものではなくて,この stringList メンバーが UserClass 以外からアクセスされるのを防ぐなどの追加の工夫は必要になります. このコードは,メソッド呼び出し List.add について,呼び出し主,this が UserClass のインスタンスで,呼び出し相手,target が list で,かつ その list が, User オブジェクトが所有している stringList であるとき,引数として added という何かオブジェクトが指定されているなら,指定されたadded が String でかつ長さが1以上,というのをチェックしています.
アスペクトを用いる利点 コンテキストごとに表明を整理できる コンテキストごとに専用のクラスが増加することを避けられる 分離しておいたほうが変更が容易 手軽に表明の導入が可能 コンテキストごとに専用のクラスが増加することを避けられる 「安全性を向上するためのクラス」が増えすぎるのは設計上うれしくない このようなアスペクトの利点は,コンテキストごとに表明を整理できることです.表明を,どんな場面で使われるかによって整理しておくと,変更が容易になりますし,手軽に表明を導入できるという利点があります.また,コンテキストごとに,ラッパークラスや継承を使用してしまうと,安全性を向上するためのクラスという,設計モデル上では重要ではないクラスが増加してしまい,あまりうれしくないので,それを避けられるというのも利点です.
(2) 複数のモジュールに分散した表明 Observer パターンの特殊形の例 Observer パターンとは attach, detach Subject + attach(observer); + detach(observer); Observer + update(); attach, detach update さて,次は,複数のモジュールに分散してしまう表明の例として,Observer パターンの特殊形を示します. Observer パターンは,オブジェクトの状態の変化を監視する処理を,監視する側Observerと,監視される側Subjectのインタラクションとして記述したものです. Observer は監視したい対象 Subject に対して,「何らかの状態変化が起きたら教えてください」ということを,Subject が持つ attach メソッドを呼び出し,自分自身を引数として渡すことで登録します.Subject は,自分自身の状態が変化したとき,Observer の update メソッドを呼び出します.Observer側は,それに対して,Subject から適宜,情報を取り出すなどの処理を行い,自分自身の状態を更新します.Observerは,この更新処理が不要になった時点で,Detachメソッドを使って,Subjectへの登録を解除します. このパターンでは,Subject は Observer について,update メソッドを持っていること以外の情報を持たなくて良く,オブジェクト間の結合を抽象化できるようになります. observer は subject に自分を登録する subject は,自身が変化したとき, observer に通知する observer は,不要になった時点で登録解除する
Observer パターンへの制約付加 付加する制約: Observer と Subject を1対1対応させる Subject は接続している Observer を知っている 別の Observer が接続してきても,容易に確認できる Observer の,他の Subject への接続をどう防止するか? observer1 subject1 observer2 subject2 接続済 新たな接続を禁止 ここで,ObserverとSubjectを1対1で対応させる,という制約を付加することを考えます. この制約は,observer1 とsubject1というオブジェクトが既に接続を行っていたとすると,別のobserver2というオブジェクトが登場して subject1 に接続要求を出したり,observer1がやはり別に存在している subject2 というオブジェクトに接続要求を行うことを禁止するというものです. Subject は自分自身が接続済みかどうか知っていますので,2つ以上のObserverが接続してきたことを容易に検出することができます.これに対して,Subject にとってみれば,接続してきた Observer が既に他の subject に接続しているかどうかを知る方法がないため,1つのObserverの複数のSubjectへの接続を防ぐことができません.
従来の解決方法 Observer がどの Subject へ接続しているかという情報を Subject からアクセス可能にする必要あり Subject は 「誰とも接続していない」 Observer だけ受け入れる Subject は,接続してきた Observer の内部状態をチェックし,接続したら Observer の内部状態を変更する 表明の検査のために実装が影響されてしまっている 制約に関する表明,実装が Observer/Subject に分散 Observer の状態に関する表明が分散 何のための表明であるかが明確でなくなる アスペクトを使わずに解決しようとすると,ObserverがどのSubjectへ接続しているかという情報を,Subject側からアクセス可能にする必要があります.そして,Subjectは,接続してきた Observer が既に他の Subject に接続していないかチェックし,また接続が完了したら,Observer 側の状態を変更することになります. 表明を検査するために,本来は不要な公開インタフェースが追加されるなど,実装のコードが影響されているということになってしまいます. また,Subject へのメソッド呼び出しで Observer の状態が変更されるというのは,Observerの状態変化に関しての記述が,SubjectクラスとObserverクラスとに分散してしまうことになり,何のための表明であるのかが不明瞭になってしまいます.
アスペクトでの解決 AspectJ を用いた記述例 Subject Observer.connected = null; Observer がどの Subject に接続したかの状態の保存・チェックをアスペクトに取り込む Subject Observer.connected = null; before(Observer observer): execution(Subject.attach(Observer)) && args(observer) { assert (observer.connected == null); } after(Observer observer, Subject subject): this(subject) && args(observer) { observer.connected = subject; これを AspectJ を用いて解決した例を示します.問題となっていた,Observerがどの Subject へ登録を行っていたか,という参照をアスペクト側で宣言しておき,その設定・値の検査などもアスペクト側で完全に解決します. ここでは,Observerの中に connected というフィールドを追加し,Subject クラスの attach メソッドの実行前に,引数として渡されたobserverクラスのconnectedメンバが空でなくてはならないことを表明として記述しています.また,接続処理が終了すると,observer の接続相手として,subject への参照を記録します.
アスペクトを用いた解決の利点 複数のオブジェクトを横断した表明のモジュール化 複数モジュールに分散させるより,見通しが良い 他の制約と「混ざらない」ため,変更や除去が容易 コンテキストに依存した表明のときと同様 表明記述用の特殊な実装を明示的に記述しておける 先の例では Observer が接続した Subject の参照管理 表明検査「専用」インタフェースを,他のモジュールに使わせない 複数のオブジェクトを横断した表明のモジュール化は,複数モジュールに表明を分散させた場合より見通しがよくなります.また,他の制約と混ざらないことから,変更や除去が容易になります. また,表明記述のための特殊な実装,先ほどの例であれば,ObserverとSubjectの参照の管理も,アスペクトの一部としておくことで,他の開発者が勝手にそのインタフェースを使ってしまうといった問題を避けることができます.
アスペクトの導入による品質への影響 Predictability を強化 Evolvability もサポート 従来は書きにくかった表明への対応 コンテキストに応じた性質の書き分け 複数のモジュールを横断した性質を書ける Evolvability もサポート コンポーネント外部からの柔軟な表明の追加・削除 デバッグ用の一時的な表明の追加 汎用的なコンポーネントの安全性向上 先ほど見てきたようなアスペクトによる表明の記述が,どのように品質に影響するかを考えてみます. まず,Predictabilityが強化されます.コンテキストに応じた性質を書き分けることや,複数のモジュールを横断した性質を記述することで,従来,表明を書きにくかった場面でも利用可能になります. また,Evolvability も向上していると考えられます.表明をコンポーネントの外部から追加したり,除去したりといった作業が柔軟に行えるので,デバッグ用の表明などを一時的に導入したり,汎用的なコンポーネントに表明を付加して安全に利用するなど,表明をより便利に使えるようになります.
関連研究: Moxa 契約による設計を支援するアスペクト指向的振舞インターフェース記述言語Moxa. 山田聖(JAIST),渡部卓雄(東工大) 出典: 情報処理学会第 52 回プログラミング研究会(SIGPRO) Java 用の表明記述言語 JML に,「表明アスペクト」 モジュールを追加 関連研究として,アスペクト指向の要素を取り入れたインタフェース記述言語 Moxa があります.これは,JAISTの山田さん,東工大の渡部先生らの研究です. こちらの研究は,Java 用のインタフェース記述言語であるJMLに,表明アスペクト,というモジュール機構を拡張したものです.
Moxa のアプローチ メソッドごとに表明を書くのではなく,表明ごとにメソッドをリストする 従来 Moxa 表明A Method 1 表明B Moxa のアプローチは,メソッドごとに記述されている表明の中で,多くの部分は共通しているだろう,という仮定に基づいています.この例は,3つのメソッドに共通して表明Aが定義されていて,またメソッド1と2は表明Bを共有しています.このように複数のメソッドで共通の性質が存在する場合に,Moxaでは「表明Aは,メソッド1と2と3のときに検査します」というように,表明を主体とした記述を行うことができ,他の表明と完全に独立した記述を行うことができます. 表明B Method 3 表明A 表明C Method 3 表明C
Moxa のモジュール機構の特徴 クラスと独立して表明を記述できる 表明が主体となった記述 主目的は,クラス内の表明の整理 同じ性質を持っているメソッドを一覧する場合に便利 初期化用メソッド,初期化が終了したら使えるメソッド,終了処理用メソッド… のようにメソッドを分類するなど Moxa で導入されている表明アスペクトと,この発表の話は,クラスのコードから表明を分離して記述しているという点では同じだと思いますが,Moxaは,主目的が,クラス内の表明を整理することとなっています. Moxa では,表明が主体となった記述ができることから,今回の発表で紹介したような,AspectJ で Assert 文をプログラム内に埋め込む方法に比べると,同じ性質を持ったメソッドを一覧しやすいという利点があります.
まとめ 表明が元々持っていた影響 アスペクトが,表明の能力を強化する 表明は Predictability を向上させる コードが何をしているかを説明する 誤った振る舞いをしていれば,検出する 表明は Evolvability を悪化させることがある 詳細すぎる表明は,再利用性などにも悪影響 アスペクトが,表明の能力を強化する Predictability を強化 コンテキストに応じた性質を書き分けておける 複数のモジュールを横断した性質を書ける Evolvability もサポート 追加や削除が容易で,再利用性への悪影響も防ぎやすい 最後にまとめます.表明とは,元々,コードが何をしているかを説明し,また誤った振る舞いを検出することから,Predictability を向上させ,システムが何をしているか,開発者の理解を促進します.また,表明を詳細に記述すればするほど,安全性は高くなりますが,不要な条件まで書き加えてしまうことでコードの再利用性が低下したり,仕様の変更時にあちこちの表明を直さなくてはならないといったように,Evolvabilityを悪化させる要因となることがあります. 表明をアスペクトとして記述すると,コンテキストに応じた性質をアスペクトとしてもとのコンポーネントとは分離して整理しておいたり,複数のモジュールを横断したような性質もまとめて書くことができます.これは,分散した表明記述と比べると,そのアスペクトを見ればオブジェクト間の性質が理解できることから,Predictability の向上が見込めますし,モジュールとして分離したことから追加や削除も容易となり,Evolvability も向上することができると考えられます.
補足資料
表明の悪影響は? 表明の記述をアスペクトに分離することで,定義が分散してかえって理解しにくくなる? 副作用のある表明 コンテキスト依存の表明は,コンポーネント本来の表明 + 「そのコンテキスト依存の表明」 だけ見つけられれば良い 利用場面に対応するクラスなどと一緒に配置することで管理が可能 複数オブジェクトを横断した表明は,元々,書きにくいものも存在 副作用のある表明 表明自体が何らかの作用を持つと,混乱を招く プログラミング言語/コンパイラによる保護の活用 C++ における const キーワードなど ここまで,利点だけを強調してきましたが,ここで,アスペクトを導入して,品質が悪化することがあるのかどうか,ということについても検討します. というのは,表明の記述をアスペクトとして分離することで,1つのコンポーネントに対して,元々の表明はそこに書いてあるとしても,利用場面に応じて,追加の表明があちこちに書かれているという状態になりますから,1つのコンポーネントについての記述が分散することで理解しづらくなるのではないか,という疑問が出てきます. 実際には,コンテキスト依存の表明というのは,各利用場面で,コンポーネント本来の表明と,そのコンテキスト依存の表明だけ見つけられれば良いので,たとえば利用する側のクラスなどと一緒に,利用時の表明を配置しておくことで,管理が可能となると考えられます. また,複数オブジェクトを横断した表明のほうは,従来の表明では書きにくいものもあるので,単純に定義が分散するから悪い,という比較は難しいと考えています. 一方で,副作用の問題というのがあります.これは,AOPの導入に限らない話で,表明として記述された条件式の中に,副作用を持つものが含まれていると,本来のプログラムの動きを損なったり,混乱を招く可能性があります.C++における const キーワードのように,副作用がないことを保証するような言語機構や,それに類するチェックツールの活用が望ましいということになります.
Moxa の特徴 クラス内の表明を整理する クラス間の関係も(たぶん)書ける 表明の記述に特化している利点 表明の数が減少する 同じ性質を持ったメソッドを一覧できる 初期化用メソッド,初期化が終了したら使えるメソッド,終了処理用メソッド… のようにメソッドを分類するなど クラス間の関係も(たぶん)書ける 表明の記述に特化している利点 副作用の問題などが発生しにくい 表明記述用の便利な関数・演算子が使える Moxa の利点は,クラス内のメソッド群が持つ性質を整理することで,表明の記述の数を減らせること,同じ性質を持ったメソッドを一覧できるといったところにあります.これは,たとえばオブジェクトの初期化用メソッドと,初期化が終了したら使えるメソッドと終了処理用メソッドを分けるといったように,何らかのグループ化を行いたい場合に,便利であろうと考えられます. また,この出典の論文ではクラス内のメソッド群だけを相手にしているのですが,言語機構としては,クラス間の関係も,書こうと思えば書けるであろう,という印象を受けています.先に述べてきたような事例も,もしかしたらうまく記述できるのかもしれない,と考えられます. AspectJ での表明記述のような一般的なプログラミング言語の機構を使った場合と,Moxaのような表明記述のために作られた言語との違いとしては,Moxaは表明に特化されているぶん,副作用などの問題が発生しにくく,安全性が高いという利点を持つと考えられます.また,表明記述用の便利な関数・演算子が使えますから,たとえばメソッド呼び出し後に成立する条件として,メソッド呼び出し前の状態と比較するといった条件は,簡単に記述することができます.
提案していた言語機構 assert(expr) の,expr の部分の関数の実装をアスペクトに追い出す cflow 述語を使う // FileIsOpen の実装は誰かアスペクトが提供してくれる // 複数提供された場合は全部の AND をとる assert(FileIsOpen()); cflow 述語を使う // この文は,Foo.foo から呼ばれたときだけ通過できる assert ( cflow ( Foo.foo() ) ); AspectJ + Annotation などで実現可能な領域に近い
安全性と再利用性 表明は安全性を高める コンポーネントの2種類の制約 安全のためには書きたい 条件を書きすぎてしまうと再利用しにくい コンポーネント本来の制約 例: このList には「任意の null でないオブジェクトを格納する」 一般的なオブジェクトに適用できるので,再利用が容易 アプリケーションが必要とする「最小限の」機能 例: このListには「長さ1以上の文字列を格納する」 誤ったオブジェクトを格納する可能性はなくなる まず,表明の安全性と再利用性の問題について説明します. コンポーネントに対して,表明の記述を追加すればするほど,その時点で多くの条件が成立していることになるため,その時点で許される状態の自由度が減少し,予測可能な範囲に収まります.その結果,コンポーネントは安全となっていきます.しかし,その分,その表明が想定している状態以外はすべて違反とされてしまうため,再利用性の低下を招いてしまいます. このような問題の例として,ここでは,コンポーネントの持つ2種類の制約を挙げます.2種類の制約のうち1つは,コンポーネントが本来持っている制約です.たとえば,ある List クラスについて,null 参照でさえなければ,どんなオブジェクトでも格納できる,というように実装できたとします.すると,格納するオブジェクトは null でない,ということがそのコンポーネントの外すことのできない制約になります. 2種類のうちもう1つは,アプリケーションが必要とする最小限の機能に関わる制約です.たとえば,List クラスを作っていた動機が,長さ1以上の文字列を管理したい,というものだった場合,長さ1以上の文字列を格納できるということがList側の制約となります. ここで,長さ1以上の文字列だけを格納する,という制約を採用すると,その List に対して誤って他のオブジェクトを格納してしまう危険性はなくなりますが,今後 List クラスを再利用していくとき,他のオブジェクトを格納したい,といった用件に対して,実際には格納できることは分かっているにも関わらず,再利用できなくなってしまいます.
「安全な再利用」のための制限 Behavioral Subtyping あるオブジェクトが,別のオブジェクトの代替となる条件 Strong (拡張ではない) Specialized Implementation require (事前条件) Original Component Extension この問題の背景には,Behavioral Subtyping という条件があります. Behavioral Subtyping とは,あるオブジェクトが,別のオブジェクトの代替となるための条件です. コンポーネントを拡張する際には,具体的には,事前条件は同じか弱くなること,つまりオブジェクトが,元のオブジェクトが動く状況では必ず動くことが要求されます.また,事後条件は同じか強くなること,つまり元のオブジェクトが保証している条件は少なくとも同じように保証すること,が求められます. しかし,先ほどの例であれば,任意のオブジェクトを格納できるList を,文字列だけを格納する List として使う場合は,事前条件,事後条件ともにより強くなってしまうので,Behavioral Subtyping の関係にはならず,相互に再利用することができません.安全性のために,そのコンテキストでのみ事前条件を強めて使用したいという要求がある場合,また,そもそも表明が記述されていないようなコンポーネントにおいて事前条件がまったくないものを再利用する場合などには,後から条件を追加・削除することが重要となってきます. Behavioral Subtyping Simple Implementation Generalization Weak Weak ensure (事後条件) Strong