What is a Design Evolution?
ソフトウェアにおけるデザイン進化とは?
まず、いくつかのレベルでのデザイン活動が考えられる。(全体としての)アーキテクチャレベルのデザイン、コンポーネント(クラスなど)レベルでのデザイン、コードレベルでのデザイン。
実際には、コンポーネントの内と外のデザインは比較的区別しやすいかもしれないが、これらのデザインを厳密に区別することは難しいかもしれない。
「マルチパラダイムデザイン」でコプリンが言っているように、デザインを考える上では、プログラマの思考の中にも厳密な区別はないかもしれない。
[...] プログラマが実務の中でどのように働いているかを調べたならば、たいていのアプリケーションの場合、彼らがアーキテクチャ、設計、実装を線引きしているわけではないことがわかるだろう。彼らの属している企業で採用されている公式の手法でそうすべきだと謳われているか否かに関わりなく、彼らの中には線引きはないのである。
ここでは、デザインパターンを例にあげて、デザインの進化を考えたいと思う。ここでのとりあえずの結論(あるいはデザイン進化の一種)は、デザインのルールの変更*1ということとしたい。
いくつかのデザインパターンは、その適用後のデザインパターンの成長に特定のパターンがある(あるいは、以下で述べるようにどんなデザインにも暗黙の成長ルールがあるかもしれない)。
たとえば、Strategy パターンが適用された状態で、新しいアルゴリズムを追加しようとしたなら、そこには拡張(成長・進化)ルールがある。つまり、Strategy クラス/interface を継承/実装して、新しい ConcreteStrategy を追加する。
一方で、もし、Strategy パターンを適用していないデザインだとしても、拡張ルールが見られる。
public class Context {
private int strategy = 1;public void setStrategy(int strategy) {
this.strategy = strategy;
}public void contextInterface() {
if (strategy == 1) {
algorithmInterface1();
} else if strategy == 2) {
algorithmInterface1();
} else {
// デフォルト or 例外
}
}private void algorithmInterface1() { ... }
private void algorithmInterface2() { ... }
}//新しいアルゴリズムを追加したい場合。
public class Context {
private int strategy = 1;public void setStrategy(int strategy) {
this.strategy = strategy;
}public void contextInterface() {
if (strategy == 1) {
algorithmInterface1();
} else if strategy == 2) {
algorithmInterface2();
} else if strategy == 3) {
algorithmInterface3();} else {
// デフォルト or 例外
}
}private void algorithmInterface1() { ... }
private void algorithmInterface2() { ... }
private void algorithmInterface3() { ... }
}
後者の if を使った例は、リファクタリングの対象となりうる(「パターン指向リファクタリング入門」)。
Strategy パターンへのリファクタリング適用することで、拡張ルールの変更が見られる。この記事では、このようにして拡張ルールが変化(進化)することを、デザイン進化(の一種)と呼びたい。
もちろん、いくつかの疑問に答えていく必要がある。
- すべてのリファクタリングはデザイン進化なのか?
- リファクタリングでないようなデザインの進化は考えられるか?
もちろん、回答すべき重要なこととして、デザイン進化を考えることにどんな意味があるのか? というものがある。
Completness of Large Refactoring and Structure Evolution
大きなリファクタリングとプログラムの構造進化との関係は?
「リファクタリング」によれば、個々の小さなリファクタリングは数分から数時間で終わるのに対して、大きなリファクタリングは数ヶ月から数年かかる。
また、大きなリファクタリングの場合、どこでやめるのかを決めることができる。たとえば、意図的に途中でやめる場合もあれば、さらにリファクタリングできることに気付かずに途中でやめてしまうこともある。
いずれにせよ、大きなリファクタリングの過程は、モジュール(クラス)間の構造の進化(あるいは構造の成長)の過程に影響を与える。
たとえば、大きなリファクタリングの途中で、あるクラスのフィールドにアクセスするために get メソッドを追加することが必要になるかもしれない。そして、結果として get メソッドを追加するとする。この後、構造の進化を追跡しようとする場合、現在進行中の大きなリファクタリングを終わらせるかどうかによって、少なくとも二つの可能性が生まれる。
- (1) 終わらせない場合:終わらせることなくリファクタリングを続けた結果、先ほど追加した get メソッドは一時的に必要だったのであり、さらにリファクタリングを続けることで、追加した get メソッドは削除される。
- (2) 終わらせる場合:この場合、get メソッドは追加されたままとなる。
1 の場合、あるクラスの構造は、リファクタリング前と後では、get メソッドが存在するかどうかの視点では、変更されていない。
2 の場合、クラスに get メソッドが追加されたままそのクラスは成長を続ける。その後、機能追加の要求などにより、そのクラスは、さきほどの get メソッドが追加されただけでなく、さまざまな可能性(たとえば、あるクラスを継承するようになったり、ある interface を実装するようになったり)で成長する。その後、先ほどの大きなリファクタリングが再開されるかもしれない。そして、その結果として、get メソッドの必要性がないことに気付き、そのメソッドは削除されることになるかもしれない。この場合、クラスの構造の進化の視点から見ると、メソッドがなくなったという点で、構造進化と言えるかもしれない。
より形式的に考えるとこうなる。まず、大きなリファクタリングは、二つの(大きな)リファクタリングに分割できるものとする(リファクタリングAとB)。また、その後の進化的な要求としてある機能を追加しなければならないとする(機能Aの実装)。
1の場合は、「リファクタリングA、リファクタリングB、機能Aの実装」という流れであり、2の場合は「リファクタリングA、機能Aの実装、リファクタリングB」となる。
1と2いずれの道を通ったとしても、プログラムの振る舞いは同じである。しかし、構造進化の視点では、微妙な違いを観察できる。先ほど述べたように、あるクラスの進化(成長)過程において get メソッドがいかに追加され削除されるかといった違いである。
この違いがどんな意味を持っているのかについては、今後の課題である。ただし、少なくとも、構造進化を観察することを目的としている場合には、リファクタリング時における構造進化と、機能追加時における構造進化を意識する必要がある(かもしれない)ということである。
Role based Evolution Dependency Structure Matrix
昨日学校のゼミで発表した資料。
REDSM の構想から、資料の完成まで10時間ぐらいで作った内容なので色々と問題点あり。
Design Evolution in Software
ソフトウェアにおける設計の進化とは何か? なぜ設計の進化を考えることが重要なのか?
「誰のためのデザイン?」では、デザインの進化の例として、電話機をあげている。良いデザインと悪いデザインの例として、電話機を床に落としてしまった時に、電話が切れないようにデザインされているかどうかを挙げている。
この場合、デザインとは、使いやすさやユーザインタフェースにおけることを言っているように見える。
ソフトウェアにおけるデザインとは? たとえば、デザインパターンにおけるデザインをデザインと呼ぶ場合、デザインの進化とは何か。何が進化するのか。
デザインとは、アクティビティである。
「マルチパラダイムデザイン」によれば、設計とは:
設計とは、ある問題に対して解決策となるような構造を与えるアクティビティのことである。
「Modern C++ Design」によれば、設計とは:
ソフトウェアシステムの設計とは、各々の解決領域を組み合わせて解決策を選択することなのです。
ソフトウェアにおけるデザインは、結果としてユーザインタフェースや機能におけるデザインに関係するかもしれない。あるソフトウェアの使いやすさを考慮することは、具体的な解決策としての実装(コード)に影響を与える。
設計の進化とは、設計の改善のことか。では、リファクタリングは設計の進化についてのアクティビティなのか。
少なくとも、デザインパターンのレベルでデザインを語るとき、そのデザインのアウトプットはコードである。設計が解決策を選択することであるなら、解決策は常に具体的なコードを表す。
では、コードの変化(進化)を観察することが設計の進化を観察することになるのか。
設計のトレーサビリティは難しい。実装から、設計が取り扱う問題、コンテキスト、選択の対象となった解決策の集合を読み取るのは困難である。
そのため、単なるコードの変化から、設計の変化を抽出することは難しい。
将棋の棋士が棋譜から学ぶのと同じようにして、ソフトウェア設計者が設計を学ぶのは(恐らく)困難である。プログラミングについては学べるかもしれない。設計については難しい。たとえば、デザインパターンを学ぶといった場合、少なくとも、問題の文脈、解決策、フォース(トレードオフ)といった情報が必要である。
ソフトウェアにおける設計の進化とは何か? 何が 進化すれば設計の進化なのか? 何が どう 進化すれば、設計の進化なのか?
関連:
ドナルド・A. ノーマン
誰のためのデザイン?―認知科学者のデザイン原論
http://www.amazon.co.jp/gp/product/478850362X/
Carliss Y. Baldwin, Kim B. Clark
Design Rules, Vol. 1: The Power of Modularity
http://www.amazon.com/gp/product/0262024667/
C. Jason Woodard.
Architectural Strategy and Design Evolution in Complex Engineered Systems.
PhD thesis, Harvard University and Singapore Management University, 2006.
http://kuala.smu.edu.sg/~jason/diss.html
Constructing and Exploring Design Spaces
設計者は、どうやって設計プロセスのアウトプットとして解決策を探し、決定する?
グラスの「Software Conflict 2.0」によれば、設計の本質とは:
The essence of design, then, is rapid modeling and simulation.
つまり、設計者は、
- (1) 問題に対する解決策のメンタルモデルを作り、
- (2) 問題に対してそのモデルを実行し(シミュレーション)
- (3) 問題を解決できなかったら、モデルを修正する
- (4) 問題を解決するまで 1〜3 を繰り返す
設計者は、問題に対する解決策で構成される設計空間を探索し、最終的に解決策を決定する。たとえば、アルゴリズムを交換可能にするという問題に対して、if 文を用いる解決策と、Strategy パターンを使う解決策が選択肢となる。
この場合、n 個の(競合する)解決策がすでに存在していると仮定している。しかし、実際には、グラスの例であげたように、解決策の候補を抽出することが、設計プロセスの段階の一つであると考えられるかもしれない。
「設計」と言った場合、「設計プロセス」とそのプロセスの結果としての出力を区別すると便利かもしれない。
上述のシンプルな例でいえば「設計プロセス」は、以下の段階で構成される:
- (1) 問題を解決するであろう解決策の候補を抽出する
- (2) 候補の中から、さまざまな制約(再利用性、進化容易性、シンプルさ、など)を考慮し、解決策の一つを選択する
出力は、実装、つまり、構造を表現するコードである。
入力として「要求(問題)」があり、出力として「構造(解決策、あるいは実装)」がある。
一方で、中間の生成物として、設計候補を考えると便利かもしれない。
たとえば、プログラミング言語の選択は、最終的な解決策を制限する。たとえば、C 言語が提供する解決策の空間と C++ が提供する解決策の空間は異なる。
たとえば「X 言語が使えれば、こういう設計にするのに」といった思考は、共通であると思う。
したがって、メンタル的には、設計の空間と解決策の空間は異なると言えるかも知れない。(理想としての)設計の空間から、(現実としての)解決策の空間へのマッピングができないことがある。
たとえば、アスペクト指向的な思考になれているなら、通常の方法でロギングを実装するという設計案よりも、アスペクトとしてロギングを実装する案が真っ先に思い浮かぶかもしれない。アスペクト指向をサポートしてない言語(たとえばC言語)を使っていたとしても、そのようなアスペクトを使う設計案としては思い浮かぶかもしれない。もちろん、解決策へのマッピングができないので、最終的な実装になることはない。