De-Refactoring or Are Designs navigatable?

タイトルの意図は「リファクタリングの逆 or デザインはnavigatable?」


リファクタリングの一般的な定義(何も参照しながら書いてないので正確ではないけど)は「プログラムの振る舞いを保ったまま、設計(コード、プログラム)を改善する」という感じ。


じゃあ、逆に:あえて設計を改悪することは可能か? あるいは、設計は、悪い方向にいったりきたりすることは可能か?


そもそも、なぜ設計を改悪する必要があるのか? 何が動機か? 恐らく、一般的なプログラマには関係の無いテーマ。関係はあるのは、新しいプログラミング言語の機構とかコンセプトとかを提案しようとしている人とか、あるいは、設計に関わる新しい手法(最近だとDIとか)を提案、あるいは、評価しようとしている人。


研究の分野 or 論文では、新しい設計技術(言語とか)を提案して、その評価を行う際に、既存のコードを対象として、その新しい技術を用いることによって改善(リファクタリング)するケースが多い。例をあげるなら、AspectJ によるリファクタリングなど。


一般的にいえば、デザインパターンがその実装に用いる言語によって影響を受けるように、リファクタリングもどんな技術を用いるのかによって影響を受ける。


少し話はとんで、新しい技術が使えるようになると、今まで経験してきた設計の勘に影響を与える。たとえば、DI (Dependency Injection) に慣れた人は、DI が存在しなかった前は、どんな設計を行っていたのだろう?


あるいは、新しい技術を使っての設計は、チャレンジとなる。「チャレンジ」ってのは、その技術が有効 or 効果的 だろう ということを感じていたとしても、実際に適用してみるまではその新しい技術を使っての設計を行う選択肢を選択したことが正解だったとは、分からないということ。

たとえばAspectJを使ってのプログラミングや設計はどのようにすればいいのだろう。AspectJ的なプログラムの設計を選んで正しかったと自信を持つには、それなりの経験が必要だと思う。実際、僕はAspectJを使って2年ほどまあまあ大きなプログラム(2,3万行。コメント含む)を書いているけど、いまだにAspectJ的な設計が正しかった、という確証はない。


この確証を得にくい理由としては、設計が正しかったかどうかの判断の重要な基準のひとつが設計の進化性 or 適応性にあるからだと思う。つまり、AspectJ的な設計を選択したとして、その後のソフトウェアの進化、つまり、機能追加などのフェーズにうまく適応できるかどうかを通って初めて、選択肢が正しかったかどうかについての確証と自信に結びつくからだと思う。


そして、話を戻して、よほど暇な人でないと、設計の選択肢の比較なんてことは行わないと思う。たとえば、AspectJ的な設計を選択したとして、わざわざ「もし、AspectJを使えなかったとしたら(たとえば普通にJava)どんな設計を行っていただろう。そして、その後のソフトウェアの進化に、各選択肢(JavaAspectJ)はどのように異なって適応していっただろう。JavaAspectJでは、どのような異なる 設計の進化パスをたどることになったのだろう。」とかを実際に問いかけて、評価・検証してみる人はいないと思う。


にも関わらず、この問いかけは重要だと思う。


と書いてみたけど、全然まとまってないのでまた今後書き直すことにする。

Analyzing the History and Lifetime of Case Studies in Software Design Technologies

英語タイトルの意図するのは、ソフトウェア設計に関わる技術の有効性を評価・検証・比較する時に用いられるケーススタディや例題の歴史や寿命を分析しよう、という感じ。


ここでいう、ソフトウェア設計技術ってのは、プログラミング言語であったり、パターンであったり、リファクタリングであったり、その他テクニック(IoC or DI)であったりする。直接設計に影響を与えないようなツール(リファクタリングブラウザ、メトリクス、その他)は、(とりあえず)除く。


で、こういう技術は、簡単なケーススタディとか例題とかを使って評価・検証・比較されることが多い(少なくとも僕が調べた範囲でここ数年の研究だと)。


と同時に、同じ例題が、同じ研究者や異なる研究者に使いまわされることも多い。たとえば、10年前に Sullivan が博士論文で Mediator の例としてあげた例は、今なお使われている。

Sullivan, K.J.,

Mediators: Easing the Design and Evolution of Integrated Systems,

Ph.D. Dissertation, University of Washington Department of Computer Science and Engineering, Technical Report UW-CSE-94-08-01, August, 1994
http://www.cs.virginia.edu/~sullivan/publications/mediators_phd.html

AOP コミュニティでは、(歴史は浅いかもしれないけど)Figure Editor の例が使われまくってる。

Gregor Kiczales, Erik Hilsdale, Jim Hugunin, Mik Kersten, Jeffrey Palm and William G. Griswold

An Overview of AspectJ

ECOOP 2001

http://www.cs.ubc.ca/~gregor/

他にも、Mixin 関連で見られる graph traversal application なんかも今でも例題として使われてる。

Ian M. Holland.

Specifying reusable components using contracts.

ECOOP'92

http://www.ifs.uni-linz.ac.at/~ecoop/cd/tocs/t0615.htm


そう、なぜ、ケーススタディの寿命、あるいは、歴史を調べ分析する必要があるのか? 単なる趣味なのか?


理由あるいは仮説あるいは疑問は:

・もし、同じ例題が使いまわされる傾向があるのなら、その例題を使って提案された手法・技術は、その例題以外のどこで有効なのか。その他の例題を使って、提案手法はどのくらい有効性が検証される傾向があるのか。

・そもそも、その例題は実践的に遭遇する代表的な例題なのか。その例題はどのようにして作られたのか。提案手法のデモンストレーションのために後から作り出された例題なのか。それとも、実際のコード例から抽出され、問題のエッセンスを表す例題なのか。

・その例題は、当時の問題と現在の問題に今なお一致する代表的な例題なのか。


これらの質問に答えるためには、実際にコードを調査する必要があると思う。幸い、オープンソースとかのおかげで、調査対象として使えるコードはいっぱいある。

ソースコードの中に、どのくらいに実際に例題が存在するのか」

この質問に答えるのは、技術的に結構難しいかもしれない。たとえば、デザインパターンがどのくらい実際に使われてるのかを調査するための、パターン検出の研究が数年前から行われている。しかし、デザインパターンのように抽象化されており、クラス構造や関連、あるいは、クラス持つロールなのが理解されている対象でも、自動的に検出することは今なお難しいといわれている。

Joseph (Yossi) and Gily Itay Maman

Micro Patterns in Java Code

OOPSLA'05

http://www.cs.technion.ac.il/~imaman/

もちろんデザインパターンの検出と例題の検出は違うかもしれない。


まとめ:
設計技術の検証は、難しい。現実的なプロジェクトに提案技術を導入したり、提案技術を使って一からスクラッチで作って有効性を検証するのはコストと時間がかかるため、多くの場合、簡単な例題やケーススタディを使って提案技術の評価・比較・検証が行われる。

しかし、その例題やケーススタディ自体の現実性というのは、あまり注目されていないように思える(もちろん、多くの研究者は気付いているはずだけど、実際には行動には出ていないきがする)。

例題やケーススタディの寿命や使われ方の歴史を分析することは、色々な疑問を生み出す。「その例題は代表的か」「その例題は今でも有効か」

例題の使用を実際のコードから検出することは、これらの疑問に答える一つの方法だと考えられる。

関連文献:

Zelkowitz,MV, Wallace,DR
Experimental Models for Validating Technology
http://binf.gmu.edu/weller/pages/BINF633_f2005_CourseDescription.htm
IEEE Software 1998


Walter F. Tichy
Should Computer Scientists Experiment More?
http://www.idi.ntnu.no/emner/dif8916/


M.S. Feather, S. Fickas, A. Finkelstein & A. van Lamsweerde

Requirements and Specification Exemplars
Automated Software Engineering; 4(4) pp. 419-438, 1997;
http://eis.jpl.nasa.gov/~mfeather/Publications.html


Susan Elliott Sim
A Theory of Benchmarking with Applications to Software Reverse Engineering
Ph.D. Dissertation, 2003
http://www.ics.uci.edu/~ses/phd/

Characterizing Modules from Structure Evolution Perspective

英語あってるのか不明。

意図は、モジュール構造の進化の視点から、モジュールの種類を特徴づけよう、ということ。


ここでいうモジュールは、クラスとかアスペクトとか、ソフトウェアを構成する基本的な単位。


個々のモジュールは、変化 or 進化していく。ソフトウェアの要求に対応していく過程で、あるクラスは、新しいメソッドが追加されるかもしれないし、あるいは、新しい継承階層の一部になるかもしれない。または、リファクタリングを通して、特定のデザインパターンが適用された状態になるかもしれない。


しかし、すべてのモジュールが、同じ進化の過程を同じ可能性で通るわけではない。


たとえば、XxxUtils とかいう static なメソッドを集めたクラスが、Observer のロールを将来的に行うようになるとは考えがたい。


つまり、あるモジュールがどのような進化の過程を通るのかには、ある種の特徴があると考えられる。または、進化の過程自体が繰り返し起こるパターンになるかもしれない、と考えることもできる。


もちろん、実践的な視点からの疑問は「なぜ、そのようにしてモジュールを特徴付ける必要があるのか?」 ということ。


この疑問への回答は、まだ考え中。


関連文献

Joseph (Yossi) Gil and Itay Maman.

Micro Patterns in Java Code.

OOPSLA'05.

http://www.cs.technion.ac.il/~imaman/

Danny Dig and Ralph Johnson.

The Role of Refactorings in API Evolution.

ICSM'05.

https://netfiles.uiuc.edu/dig/www/research.html

最近のソフトウェア進化研究の概要を知りたいなら、たとえば:

Tudor Gîrba

Modeling History to Understand Software Evolution.

Ph.D. thesis, University of Berne, November 2005.

http://www.iam.unibe.ch/~scg/cgi-bin/oobib.cgi?query=Girb05d&abstract=yes

T. Mens, M. Wermelinger, S. Ducasse, S. Demeyer, R. Hirschfeld and M. Jazayeri.

Challenges in Software Evolution.

8th International Worshop on Principles of Software Evolution.

http://w3.umh.ac.be/~infofs/preprints/index.php?page=author_info&ID=5

Bad Design is Good Design?

何がよい設計なのかは、議論がわかれるところ。一つ言えるのは、よい設計というのは、与えられた道具 or 言語やコンセプトに依存する。

たとえば、オブジェクト指向設計の視点では、Observer パターンは、よい設計だといえる。というのも、通常、Observer以外には適切な設計を見つけるのは難しいため or パターンがなぜパターンとして有効なのかということから明らか。

でも、アスペクト指向の視点では、(少なくとも研究コミュニティで主張されている)もはや、良い設計とは、いえない。なぜなら、オブジェクト指向の Observer は横断的関心事を持つ例だから。

さらに一般的には、パターンかどうかに限らず、今まで良い設計だとされてきた設計が、新しい言語や手法の導入によって、もはや必ずしも良い設計だとは言えなくなることがある。or 新しい設計手法を提案する人は、現状の設計に問題が あるので、その欠点をなくす手法や改善する手法を提案する。通常、言語設計では、これは、言語拡張や新たなコンセプトを導入することによって行われる。


当たり前の話に思えるかもしれない。


でも、逆のことが起こりうる。今まで悪い設計だと思われていたのが、新しい言語やコンセプトの導入によって、逆に良い設計になることもありうる。具体的な例はまだ示せないけど、それが起こる場合がある気がする。あるいは、良いか悪いかの判断が微妙になることがある(あると感じるから、今ここに書いている)。「判断が微妙になる」というこの感覚事態が、良い設計か悪い設計かどうかのお互いの距離は、必ずしも明らかなほど離れていないことを意味する気がする。


なぜ、それを良い設計だと思った?
なぜ、それを悪い設計だと思った?
それは、まだ 良い設計か?
それは、もう 良い設計か?

Contextual Polymorphism?

最もよく知られているポリモーフィズムの形は、オブジェクト指向のもの。この場合、メソッドの振る舞いは、どのオブジェクトかに依存して決まる。

最近、Mezini らの Caesar 言語によって、AOP 言語における新しいポリモーフィズムの形が提案されている。簡単に言えば、Aspectual Polymorphism とは、オブジェクトの振る舞いは、さらに、どのアスペクトのがデプロイされているのかによって決まる。


さらには、どのクラス(or コンテキスト)内でそのメソッドを呼び出すかによって振る舞いが変わるようにできると便利かも?

たとえば、アルゴリズムAとアスゴリズムBがあって、実行結果をログしたいとする。ログの出力先のディレクトリは、AとBで異なるようにしたい。


public class AlgorithmA {
public void run() {
// ...
log(...);
}

private void log(...) {
String logDir = LogDirUtil.getDir() + "/AlgorithmA" + ;
String logFile = logDir + "/result.text";
// log
}
}

public class AlgorithmB {
public void run() {
// ...
log(...);
}

private void log(...) {
String logDir = LogDirUtil.getDir() + "/AlgorithmB" + ;
String logFile = logDir + "/result.text";
// log
}
}

アルゴリズムクラスは、どこが出力先のディレクトリなのかを知っている必要があるのか?


で、たとえば、アノテーションとか利用してコンテキストを明示的に表現できるように?


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface LogContext {
public String value();
}

public aspect LogDirectoryContext {

String around(LogContext context) :
@within(context) && call(String LogDirUtil.getLogDir())
{
return proceed(context) + "/" + context.value();
}
}

@LogContext("AlgorithmA")
public class AlgorithmA {
public void run() {
// ...
log(...);
}

private void log(...) {
String logFile = LogDirUtil.getDir() + "/result.text";
// log
}
}

@LogContext("AlgorithmB")
public class AlgorithmB {
public void run() {
// ...
log(...);
}

private void log(...) {
String logFile = LogDirUtil.getDir() + "/result.text";
// log
}
}

単にコードがアノテーションに移っただけとも解釈できるけど、気分的にはすっきりする感じ?


Aspectual Polymorphism:

Mira Mezini and Klaus Ostermann
Conquering Aspects with Caesar
AOSD'03


Mira Mezini and Klaus Ostermann
Variability Management with Feature-Oriented Programming and Aspects
Foundations of Software Engineering (FSE-12), ACM SIGSOFT, 2004.


Christoph Bockisch, Michael Haupt, Mira Mezini and Klaus Ostermann
Virtual Machine Support for Dynamic Join Points
AOSD'04

DL: http://www.st.informatik.tu-darmstadt.de/public/Publications.jsp


Context Oriented Programming:

Pascal Costanza and Robert Hirschfeld

Language Constructs for Context-oriented Programming - An Overview of ContextL,

Dynamic Languages Symposium, co-located with OOPSLA'05, October 18, 2005, San Diego, California, USA.

DL: http://p-cos.net/research.html

Crosscutting in Advice/Pointcut Binding

AOP の本来の動機は、従来のモジュール化の方法(手続き型、オブジェクト指向、etc)では発生してしまう Crosscutting な要求をよりうまくクリーンモジュール化しようということ。


でも、アドバイスとポイントカットの定義自体にも、Crosscutting っぽいのが発生してしまうと思う。


たとえば、以下の例では、同じジョインポイントを参照している3つのアスペクトを示している。


public class MyClass {
public void method() { ... }
}

public aspect MyAspectA {

before() : call( void MyClass.method() ) {
...
}
}

public aspect MyAspectB {

before() : call( void MyClass.method() ) {
...
}
}

public aspect MyAspectC {

before() : call( void MyClass.method() ) {
...
}
}

分かるように、もし、参照したいジョインポイント(この例では、call( void MyClass.method() ))に変更があったら、3つのアスペクトともに変更が及ぶ。解決策の一つは、共通で参照するポイントカットを定義すること:

public class MyClass {
public void method() { ... }
}

public aspect CommonPointcuts {
public pointcut MyClassMethodCall() : call( void MyClass.method() );
}

public aspect MyAspectA {

before() : CommonPointcuts.MyClassMethodCall() {
...
}
}

public aspect MyAspectB {

before() : CommonPointcuts.MyClassMethodCall() {
...
}
}

public aspect MyAspectC {

before() : CommonPointcuts.MyClassMethodCall() {
...
}
}

それでも、まだ、アドバイスとポイントカットを結びつけるさいには、コードの重複が発生してしまう。現在の AspectJ の実装では、この重複をなくすことは難しい。


ありえる解決策は、アドバイスに名前をつけられるようにして、さらに、アドバイスにポイントカットの定義を外部からバインドできるようにすること:


public class MyClass {
public void method() { ... }
}

public aspect CommonPointcuts {

public pointcut MyClassMethodCall() : call( void MyClass.method() );

bind : MyAspect*.my() : MyClassMethodCall()
}

public aspect MyAspectA {

before my() {
...
}
}

public aspect MyAspectB {

before my() {
...
}
}

public aspect MyAspectC {

before my() {
...
}
}

ただ、アドバイスの定義だけをみても、どんなジョインポイントにバインドされるのかは分からないという欠点もある。でも、この欠点は、AJDT などの AOP をサポートする開発環境があれば、軽減されると思う。


参考:
僕が勧めても仕方がないけど、AOP の動機とかの記述で一番簡潔な気がするのは、この論文の Introduction の部分です。

Jürgen Hallpap
Towards Caesar: Dynamic Deployment and Aspectual Polymorphism
http://www.st.informatik.tu-darmstadt.de/public/Thesis.jsp?id=15

Aspectual Polymorphism

まだまだマイナな単語だと思うけど「Aspectual Polymorphism」についての覚書(なので or なのだけど、読者を限定した書き方になってしまうかも&用語とか適当に使う)。いつかどこかにちゃんとした形でまとめるかもしれないけど。

"Polymorphism" という用語から分かるように、オブジェクト指向におけるポリモーフィズムと同じような感じに、アスペクト指向における、ポリモーフィズムとはなんでしょう、という話。

これは比較的新しい話題なので「Aspectual Polymorphism って何?」という話ではなく「Aspectual Polymorphismとは何であるべきか? 何を Aspectual Polymorphism と呼べば良いのか?」という話。

Aspectual Polymorphism はAOP 言語の一つである Caesar の文脈でいえば「オブジェクトの振る舞いは、通常のポリモーフィズムだけで実行時に決定されるのでなく、そこに処理が移った時点で、どのアスペクトがデプロイされているかにも依存する」というニュアンス(だと思う)。

たとえば、以下のような Caesar のコードがあるとして:


public cclass SimpleLogging {

before() :
call( void Point.setX(int) ) || call( void Point.setY(int) )
{
System.out.println("SimpleLogging before setX");
}
}

public cclass VerboseLogging extends SimpleLogging {

before() : call( void Point.setColor(String) ) {
System.out.println("VerboseLogging before setColor");
}
}

public class Main {

public static void main(String[] args) {
run("");
run("verbose");
}
private static void run(String mode) {

System.out.println("mode: " + mode);

SimpleLogging logger;
if ( mode.equals("verbose") ) {
logger = new VerboseLogging();

} else {
logger = new SimpleLogging();
}

deploy( logger ) {

Point p = new Point(1, 2);

p.setX(10);
p.setY(20);
p.setColor("red");
}
}
}

実行結果は以下のようになる:


mode:
SimpleLogging before setX
SimpleLogging before setX
mode: verbose
SimpleLogging before setX
SimpleLogging before setX
VerboseLogging before setColor

つまり、deploy に渡されたアスペクトインスタンスによって deploy 内の振る舞いがかわる。現在いわれている Aspectual Polymorphism っていうのはこういう雰囲気。

でも、Aspectual Polymorphism であることの条件ってなに?
たとえば、SimpleLogging と VerboseLogging が同じクラス階層に属していなくても、Aspectual Polymorphism と呼んでも良いのか?


public cclass SimpleLogging {
before() :
call( void Point.setX(int) ) || call( void Point.setY(int) )
{
System.out.println("SimpleLogging before setX");
}
}

public cclass VerboseLogging {

before() :
call( void Point.setX(int) ) || call( void Point.setY(int) )
{
System.out.println("VerboseLogging before setX");
}

before() : call( void Point.setColor(String) ) {
System.out.println("VerboseLogging before setColor");
}
}

public class Main {

public static void main(String[] args) {
run("");
run("verbose");
}

private static void run(String mode) {

System.out.println("mode: " + mode);

Object logger;
if ( mode.equals("verbose") ) {
logger = new VerboseLogging();

} else {
logger = new SimpleLogging();
}

deploy( logger ) {

Point p = new Point(1, 2);

p.setX(10);
p.setY(20);
p.setColor("red");
}
}
}

実行結果はさっきと同じ。

何がいいたいのかっていうと、オブジェクト指向では、同じ階層に属するってのがコンパイラに基本的には要求される:


Shape s1 = new Point(); // ok
Shape s2 = new Person(); // no
このような型要求は、現在の Aspectual Polymorphism にはないような気がする。
問題は、このような要求はなくてもよいのか、それとも、あるほうがいいけど、まだ 実現されていないだけのか、ということ。


もう一つは、deploy 外でデプロイされているアスペクトの存在。


public class Main {

public static void main(String[] args) {

DeploySupport.deployBlock( new SimpleLogging() );

run("");
run("verbose");
}

private static void run(String mode) { // 変更なし

System.out.println("mode: " + mode);

Object logger;
if ( mode.equals("verbose") ) {
logger = new VerboseLogging();

} else {
logger = new SimpleLogging();
}

deploy( logger ) {

Point p = new Point(1, 2);

p.setX(10);
p.setY(20);
p.setColor("red");
}
}
}

実行結果は:


mode:
SimpleLogging before setX
SimpleLogging before setX
SimpleLogging before setX
SimpleLogging before setX
mode: verbose
VerboseLogging before setX
SimpleLogging before setX
VerboseLogging before setX
SimpleLogging before setX
VerboseLogging before setColor

なんの違和感もない?