asatoの技術的な日常日記

「成長に最大の責任をもつ者は、本人であって組織ではない。自らと組織を成長させるためには何に集中すべきかを、自らに問わなければならない」  非営利組織の経営 - ピーター・ドラッカー

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

デザインプロセスとデザインデシジョン


Robert P. Smith and Steven D. Eppinger

A Predictive Model of Sequential Iteration in Engineering Design

Management Science, vol. 43, no. 8, pp. 1104-1120, August 1997

DL: http://web.mit.edu/eppinger/www/publications.html


に次のような文があったのが考えてみる(太字は僕による)。

In studying engineering design, it has been observed that a complete design process is divisible into discrete design decisions (Marples 1961, von Hippel 1990). Each decision is made in consideration of previous decisions and is subject to change based on later results.


デザインプロセスは、デザインデシジョンに分割できる、ってところだけど、図で描くとこんな感じか。


decision.png



ソフトウェアデザインにおけるデザインデシジョンとは何か。Cai が、Constraint Network と呼ばれる形式的なモデルを用いて説明しているので、それを見てみよう。

Yuanfang Cai

Modularity in Design: Formal Modeling and Automated Analysis

PhD Dissertation.

DL: http://www.cs.virginia.edu/~yc7a/Publications.htm


CN は、設計次元や外部状況をモデル化するための形式的なモデル。次のような要素から成る:
-変数:デザインデシジョンが行われる次元を表現
-値:デザインデシジョンを表す
-ドメイン:値の集合から成る
-論理的制約:要求される関係をモデル化

論文でも用いられているけど、例として行列クラスを考えてみる。このクラスのデータ構造は、クライアントがどうやって使うかに依存する。array は、密行列(dense matrix)に有効。linked list は、疎行列(sparse matrix)に有効。また、このクラスのメソッドの実装するアルゴリズムは、データ構造に対応しなければならない。

ここで、データ構造やアルゴリズムを設計次元とし、クライアントの要求を、外部状況として扱う。

Constraint Network を使って、この行列クラスの設計をモデル化するとこうなる。

scalar matrix: (dense, sparse)
scalar data_structure: (list_ds, array_ds, other);
scalar algorithm: (list_alg, array_alg, other);
data_structure = array_ds => matrix = dense;
data_structure = list_ds => matrix = sparse
algorithm = array_alg => data_structure = array_ds
algorithm = list_alg => data_structure = list_ds

cn.png


他にも例えば、Observer パターンを CN で表すとこんな感じ(より詳細なやつは論文を参照)。

scalar policy_notify: (push, pull) // 通知ポリシー。
scalar subject_to_observer_map: (hashtable, other); // どうやって subject と observer をマップするか。



最初のテーマとの関係性を何か言うつもりだったのだけど、発散してしまった感がある。

一つ考察するなら、デザインプロセスとは、CN を構築し、更新し、デシジョンを行うことを繰り返すプロセスといえるかもしれない。問題解決(=デザイン)のプロセスの初めから、設計空間を完全にモデル化できるわけでもないし、途中で問題が変われば、つまり、要求変化があれば、CN を更新する必要がある。
スポンサーサイト

ill-defined problems

更新履歴:
2008.01.05:原文を載せました。誤字・脱字を直しました。
2007.12.20:関連記事のリンクを載せました。
2007.12.18:誤字・脱字を直しました。


Nigel Cross

Engineering Design Methods: Strategies for Product Design (ペーパーバック)

http://www.amazon.co.jp/dp/0471872504/


ill-defined problem の特徴がうまくまとめられてそうだったのでメモ。

設計者が取り組む問題は、ill-defined とか ill-structured とかに見なされるらしい。ill-defined とか ill-structured とかの反対の問題は、チェスとかクロスワードとかで、well-defined とか well-structured とかの問題。

well-defined な問題の特徴は、明確なゴールがあったり、多くの場合には正しい一つの問題があったり、答えを出す手順が知られていたり、とからしい。

一方、ill-defined な問題の特徴はこんな感じらしい(以下、微妙な翻訳)。
-問題に関する決定的な(definitive)定式化はない:問題が最初に設定されたとき、ゴールは不明確で、制約や基準の多くは不明である。問題の文脈は、複雑でありちらかっており、良く理解されていない。問題解決のプロセスにおいて、問題の一時的な定式化はフィックスされるかもしれないが、不安定であり、より多くの情報が利用可能になるにつれて変化する。

-問題に関するどんな定式化も非一貫性を含みうる:問題は、内部的に一貫していることは多くない。多くのコンフリクトや非一貫性は、解において解決されなければならない。しばしば、非一貫性は、問題解決のプロセスにおいてのみ現れる。

-問題の定式化は、解決策依存:問題を定式化する方法は、解決する方法に依存する。暗黙・明示的に解のコンセプトを参照することなしに問題を定式化することは困難である。

-解決策の提案は、問題理解の手段の一つである:問題に関する多くの仮定や、不確実性は、解のコンセプトを提案することのみで明らかになる。多くの制約や基準は、解決案を評価することの結果として現れる。

-問題に対する決定的な解決策はない:異なる解決策は、初期の問題に対する同等に妥当な反応となりうる。解に対する客観的な true-or-false の評価はなく、解は、良いか悪い、適切か適切でないかで評価される。


以下原文。
There is no definitive formulation of the problem: When the problem is initially set, the goals are usually vague, and many constraints and criteria are unkhown. The problem context is often complex and messy, and poorly understood. In the course of problem-solving, temporary formulations of the problem may be fixed, but these are unstable and can change as more information becomes available.

Any problem formulation may embody inconsistencies: The problem is unlikely to be internally consistent; many conflicts and inconsistencies have to be resolved in the solution. Often, inconsistencies emerge only in the process of problem-solving.

Formulations of the problem are solution-dependent: Ways of formulating the problem are dependent upon ways of solving it; it is difficult to formulate a problem statement without implicitly or explicitly referring to a solution concept. The way the solution is conceived influences the way the problem is conceived.

Proposing solutions is a means of understanding the problem: Many assumptions about the problem, and specific areas of uncertainty can be exposed only by proposing solution concepts. Many constraints and criteria emerge as a result of evaluating solution proposals.

There is no difinitive solution to the problem: Different solutions can be equally valid responses to the initial problem. There is no objective true-or-false evaluation of a solution; but solutions are assessed as good or bad, appropriate or inappropriate.

ill.png



関連記事:
本・デザイン:Sketches of Thought

Ultra-Large-Scale Systems - The Software Challenge of the Future

Kevin Sullivan さんのページ久しぶりに見てたら、

 Ultra-Large-Scale (ULS) Systems: The Software Challenge of the Future

という本?があったので気なったりした。

どうやら「Ultra-Large-Scale (ULS) Systems」というプロジェクトがあるらしい。

ワークショップも何度か開催されているようだし興味深い。


 

製品進化とダーウィンプロセス

ほとんどメモ。


製品進化を説明するためにダーウィンの生物進化のプロセスをアナロジーとして用いることの問題点が以下の論文で軽く載ってた。

Wissmann, L., Yassine, A.,

"Discrete Design Adaptation within a Dynamic Environment,"

PDRL working paper # PDL-2005-03.

DL: http://www.iese.uiuc.edu/pdlab/Publication.htm


この論文では、ダーウィンプロセスは次の図で表されてた。
dawin.png


製品進化を説明するためにダーウィンの生物進化のプロセスをアナロジーとして用いることの問題点は:

(1)ダーウィン進化では、アダプティブエージェント(製品設計者)を用いることなく、変種のランダム生成のみを当てにしている。

(2)経済システムでは、製品の適合性(fitness)は、セレクティブエージェント(顧客)によって決まる

(3)ダーウィンによる製品進化では、顧客のニーズを考慮していない。これは、多くのマーケティング理論と一致しない。マーケティング理論では、大きなゴールとして顧客のニーズを満たそうとする。

(4)価格は、ランダムでない。
(5)広告メッセージは、ランダムでない。
(6)流通戦略(Distribution Strategy)は、ランダムでない。

ってなことから、ダーウィンの話を製品進化に結びつけるのはよろしくないんじゃない? ってことらしい。


もちろん、僕が興味あるのはソフトウェア進化だから、安易にダーウィンな話を持ち出すのは良くないかも、ってことを学んだのであった。

変更容易に関する研究

変更が容易 or 進化が容易なソフトウェアを開発するための方法論や原理原則を見つけるのが僕の今の研究テーマ。

特に、要求変化、それに伴うデザイン変化、そして変化のパターンの観点から攻めていきたい。


vibha sazawal さんが、変更容易(ease of change)を取り扱っている研究を、大きく4つに分類している(「6章:関連研究」)ので、それを軽くまとめたい。

Connecting Software Design Principles to Source Code for Improved Ease of Change

PhD Dissertation, December 2005.

DL: http://www.cs.umd.edu/~vibha/


一つ目のカテゴリは、変更を容易にするためのメカニズムについての研究。彼女によれば、言語、デザインパターン、モデルなどがこのカテゴリに含まれる。

二つ目は、プログラマが設計問題を特定したり、再構築を計画するのを支援するツール。


三つ目は、コードのビューを提示するようなツールに関する研究。

四つ目は、モデル駆動開発のツールに関する研究。

eoc_category.png


一つ目のカテゴリのアプローチの基本的な前提は、変更容易なソフトウェアを表現するのを容易にするメカニズムや表現を作ることができる、というもの、らしい。

でも、単にメカニズムが存在するだけでは変更容易のためには不十分。メカニズムは、適切に使われなければならない。


第一のカテゴリを、彼女は、さらに3つのセクションで議論している。その三つってのは:
(1)情報隠蔽のための言語や言語のようなメカニズム
(2)疎結合low coupling)のための言語や言語のようなメカニズム
(3)デザインパターン

一つ目の情報隠蔽のセクションでは、以下が議論の対象になってる。
-オブジェクト指向プログラミング
-アスペクト指向プログラミング
-アダプティブプログラミング
-ArchJava
-関数型言語

二つ目の疎結合のセクションでは、以下が議論の対象になってる。
-ロールに基づくプログラミング
-Abstract, parameterized components
-implicit invocation and mediators


ここまで書いて力尽きたので続きはまた今度。

First International Conference on Design Science Research in Information Systems and Technology (DESRIST 2006)/Science of Design Symposium, 2007

 Science of Design wiki

のページ見てたら発見したんだけど、デザイン系のコンファレンス/シンポジウムの情報。

 First International Conference on Design Science Research in Information Systems and Technology (DESRIST 2006)

こっちはもう終了してるけど、プロシーディングがダウンロードできるみたい。デザイン理論とか気になる論文がちらほら。

 Science of Design Symposium, 2007 - A multidisciplinary approach to creativity and design

こっちは三月に開催らしい。

アーキテクチャ・メタプログラミング

Don Batory さんの次の論文を読んだ。

D. Batory,

Program Refactorings, Program Synthesis, and Model-Driven Design,

Invited Presentation, European Joint Conferences on Theory and Practice of Software (ETAPS) Compiler Construction Conference (April 2007).

DL: http://www.cs.utexas.edu/users/schwartz/pub.htm


色々適当に解説っぽいことをしながら、学んでみることにしよう。

「アーキテクチャメタプログラミング(Architectural metaprogramming)」とは何か。

プログラムリファクタリング、アスペクト指向などのプログラム合成、モデル駆動開発なんかは、彼によれば、アーキテクチャメタプログラミング技術とみなせるらしい。というのもこれらの技術は、プログラムを値とみなし、関数(変換)を使うことで、プログラムのプログラムにマップするため。

図で書くとこんな感じか(図は僕のオリジナル)。
arch_meta.png

arch_meta2.png



論文は、アーキテクチャメタプログラミングからではなく、ソフトウェアの複雑性の話から始まっている。

20年前に、Brooks さんは、プログラマーは、本質的な複雑性(essential complexity)ではなく、偶発的な複雑性(accidental complexity)において多くの時間を費やしている、と観測したらしい。

Batory さんによれば、多くの場合、これらの複雑性の違いをうまく言い表せないらしい。

それはさておき、Brooks さんの話によれば、複雑性には、上記の二つがあることになる。
complexity.png


この二つ複雑性について詳しくは、僕は知らないけど、Batory さんによれば、複雑性というのは、構造に関して制約を強いる(imposing structure)ことで制御できるらしい。で、この論文は、ソフトウェア構造の本質的な複雑性についてのものらしい。

で、プログラムリファクタリングとか、プログラム合成、モデル駆動開発なんかは、プログラム構成についてのもの。

ちなみに、論文では「ソフトウェア構造」と「プログラム構造」は厳密に区別していないように見える。


で、次に、アーキテクチャメタプログラミングの基本的なことについての話に移ってる。

上述のように、アーキテクチャメタプログラミングでは、プログラムを値と見なす。例として、次のような「値 C」と「値 D」が紹介されてた。

class c { // 値 C
int x;
void inc() {
x++;
}
}
class d { // 値 D
int compute() { }
}



値は加算することができる。加算 C + D は、クラス c と d からなるプログラム。

次の例。
arch_meta_eg.png



この後、足し算と引き算の特性についての議論があったけど省略。


次に、distributive transformations (DTs) と呼ばれるオペレーションについての話題。

DT の例は、rename(p, q, r) があるらしい。これは、プログラム p における名前 q を r で置き換えるらしい。

たとえば、名前 "x" を "x" で置き換えたいなら、こうする: rename(C2, xx, y)。この適用により、値 C3 が生じる。
rename.png



ここまで書いて力尽きたので続きはまた今度。

コード進化:向きを変える

コード進化を考えるパート3ぐらい。ちなみに、「コード進化のパターン」と「コード進化」は、関連するけど、区別してます。


今回は、ちょっとアプリケーション特化(ゲーム)な内容だけど、機能追加の要求がどのようにしてコードに影響を与えるのかを考察したい。

どんな機能追加か。次の図のような感じ。
look.png

追加したい機能は、右側のヤツ。既に左側の機能は実現済み。

機能追加を要求変化だと考えると、要求の変化は次のような感じ。
look_req.png


この要求変化に伴って、コード側もこの要求を満たすように変化しないといけない。ここでは、コード=デザインとしてる。
look_req_design.png


結論から言えば、コードの変化があったのは次の二つのクラス:
-KeyManager:どのキーがどんな機能に対応するのか管理 or キーコンフィグの実装。
-NormalModeKeyHandler:通常モードで押されたキーの処理を扱う。

追加されたクラスは:
-LookKeyHandler:通常モードでキャラの方向を変えるキーが押されたときの処理を扱う

先ほどの図で表すとこうなる:
look_req_design2.png


いつものように、デザイン構造の変化を DSM (Design Structure Matrix) で確認してみる。DSM は、要素間の依存関係を表現するための方法。
before:
look_dsm_before.png

after:
look_dsm_after.png


別の表現だとこんな感じ。

before:
look_dep_before.png

after:
look_dep_after.png


一応コードも一部載せてみる:
before:


public class KeyManager {

// アクションキー
private static int ATTACK_KEY = KeyEvent.VK_Z;
private static int MAP_KEY = KeyEvent.VK_A;
private static int STATUS_KEY = KeyEvent.VK_X;
private static int ITEM_KEY = KeyEvent.VK_C;
private static int WAIT_KEY = KeyEvent.VK_S;
private static int SYSTEM_KEY = KeyEvent.VK_SPACE;

// その他のキーフィールド

private static int MOVE_UP_KEY = KeyEvent.VK_UP;
private static int MOVE_DOWN_KEY = KeyEvent.VK_DOWN;
private static int MOVE_LEFT_KEY = KeyEvent.VK_LEFT;
private static int MOVE_RIGHT_KEY = KeyEvent.VK_RIGHT;

// その他のキーチェックメソッド

// 前後左右
public boolean isMoveUpKey(KeyEvent e) {
return isKey(e, MOVE_UP_KEY, MOVE_UP_KEY2, MOVE_UP_KEY3);
}
public boolean isMoveDownKey(KeyEvent e) {
return isKey(e, MOVE_DOWN_KEY, MOVE_DOWN_KEY2, MOVE_DOWN_KEY3);
}
public boolean isMoveLeftKey(KeyEvent e) {
return isKey(e, MOVE_LEFT_KEY, MOVE_LEFT_KEY2, MOVE_LEFT_KEY3);
}
public boolean isMoveRightKey(KeyEvent e) {
return isKey(e, MOVE_RIGHT_KEY, MOVE_RIGHT_KEY2, MOVE_RIGHT_KEY3);
}

// ... ナナメ
}
public class NormalModeKeyHandler implements GameModeKeyHandler {

private ItemMenu itemMenu;

public NormalModeKeyHandler(ItemMenu itemMenu) {
this.itemMenu = itemMenu;
}

public void handleKey(KeyManager keyManager, GameManager gameManager, KeyEvent e) {
if (keyManager.isStatusKey(e)) {
// ...

} else if (keyManager.isItemKey(e)) {
// ...

} else if (keyManager.isMapKey(e)) {
// ...

} else if // その他のキー
// ...

} else if (keyManager.isMoveKey(e)) {
new MoveKeyHandler(gameManager, keyManager).handle(e);
}
}
}
public class MoveKeyHandler {

private GameManager gameManager;
private KeyManager keyManager;

public MoveKeyHandler(GameManager gameManager, KeyManager keyManager) {
this.gameManager = gameManager;
this.keyManager = keyManager;
}

public void handle(KeyEvent e) {

if ( keyManager.isMoveDownKey(e) ) {
tryMove( Direction.DOWN );
}
else if ( keyManager.isMoveUpKey(e) ) {
tryMove( Direction.UP );
}
else if ( keyManager.isMoveLeftKey(e) ) {
tryMove( Direction.LEFT );
}
else if ( keyManager.isMoveRightKey(e) ) {
tryMove( Direction.RIGHT );
}
// ... ナナメ
}

private void tryMove(Direction dir) {
// ... 移動できるなら移動
}
}


after:


public class KeyManager {

// その他のキーフィールド

private static int MOVE_UP_KEY = KeyEvent.VK_UP;
private static int MOVE_DOWN_KEY = KeyEvent.VK_DOWN;
private static int MOVE_LEFT_KEY = KeyEvent.VK_LEFT;
private static int MOVE_RIGHT_KEY = KeyEvent.VK_RIGHT;


private static int LOOK_UP_KEY = KeyEvent.VK_KP_UP;
private static int LOOK_DOWN_KEY = KeyEvent.VK_KP_DOWN;
private static int LOOK_LEFT_KEY = KeyEvent.VK_KP_LEFT;
private static int LOOK_RIGHT_KEY = KeyEvent.VK_KP_RIGHT;



// その他のキーチェックメソッド

// 左右前後
public boolean isMoveUpKey(KeyEvent e) {
return isKey(e, MOVE_UP_KEY, MOVE_UP_KEY2, MOVE_UP_KEY3);
}
public boolean isMoveDownKey(KeyEvent e) {
return isKey(e, MOVE_DOWN_KEY, MOVE_DOWN_KEY2, MOVE_DOWN_KEY3);
}
public boolean isMoveLeftKey(KeyEvent e) {
return isKey(e, MOVE_LEFT_KEY, MOVE_LEFT_KEY2, MOVE_LEFT_KEY3);
}
public boolean isMoveRightKey(KeyEvent e) {
return isKey(e, MOVE_RIGHT_KEY, MOVE_RIGHT_KEY2, MOVE_RIGHT_KEY3);
}

public boolean isLookUpKey(KeyEvent e) {
return (isMoveUpKey(e) && isShiftDown(e)) || isKey(e, LOOK_UP_KEY);
}
public boolean isLookDownKey(KeyEvent e) {
return (isMoveDownKey(e) && isShiftDown(e)) || isKey(e, LOOK_DOWN_KEY);
}
public boolean isLookLeftKey(KeyEvent e) {
return (isMoveLeftKey(e) && isShiftDown(e)) || isKey(e, LOOK_LEFT_KEY);
}
public boolean isLookRightKey(KeyEvent e) {
return (isMoveRightKey(e) && isShiftDown(e)) || isKey(e, LOOK_RIGHT_KEY);
}

public boolean isMoveKey(KeyEvent e) {
// ...
}
public boolean isLookKey(KeyEvent e) {
// ...
}


// ... その他のメソッド
}
public class NormalModeKeyHandler implements GameModeKeyHandler {

private ItemMenu itemMenu;

public NormalModeKeyHandler(ItemMenu itemMenu) {
this.itemMenu = itemMenu;
}

public void handleKey(KeyManager keyManager, GameManager gameManager, KeyEvent e) {
// ...

if (keyManager.isStatusKey(e)) {
// ...

} else if // その他のキー
// ...
} else if (keyManager.isLookKey(e)) {
new LookKeyHandler(gameManager, keyManager).handle(e);


} else if (keyManager.isMoveKey(e)) {
new MoveKeyHandler(gameManager, keyManager).handle(e);
}
}
}
public class LookKeyHandler {

private GameManager gameManager;
private KeyManager keyManager;

public LookKeyHandler(GameManager gameManager, KeyManager keyManager) {
this.gameManager = gameManager;
this.keyManager = keyManager;
}

public void handle(KeyEvent e) {

// ...

if ( keyManager.isLookDownKey(e) ) {
look( Direction.DOWN );
}
else if ( keyManager.isLookUpKey(e) ) {
look( Direction.UP );
}
else if ( keyManager.isLookLeftKey(e) ) {
look( Direction.LEFT );
}
else if ( keyManager.isLookRightKey(e) ) {
look( Direction.RIGHT );
}
}

private void look(Direction dir) {
// ... 方向変更
}
}




さらに違う表現をしてみよう。この表現の目的は、仕様間のオーバーラップを表すこと。キャラ攻撃の(既にある)仕様を加えていることに注意。これは、KeyMangaer と NormalModeKeyHandler が依存している仕様を少し複雑に見せたいため。
before:
look_concern_before.png

after:
look_concern_after.png


さて、この最後の図を基に、さらに仕様(or コンサーン)のオーバーラップを複雑にしていきたいと思う。目的は、コンサーンの分離の可能性を考えること。現在の図からは KeyMangaer と NormalModeKeyHandler は複数の仕様(キャラ移動、キャラ攻撃、キャラ向き変更に依存していることが分かる。

この依存をどう考えるべきか。たとえば、 ある仕様を機能と考えると、要求に合わせて取ったり付けたりしやすいか。ソフトウェア工学の言葉でいえば、プロダクトラインを構築できるのか。各機能をフィーチャモジュール(「Understanding Feature Modularity」を参照(すると良いかも。僕もまだちゃんと読んでないけど)としてモジュール化できるか。

というところを考えたいんだけど、力尽きたので、図だけ。
look_concern_more.png



やりたかったけどできなかったこと:
-フィーチャ合成・分割(feature composition/decompostion)について。たとえば、防御キーをフィーチャとするなど。あと、操作説明のビューへの影響について。

-仕様 or コンサーン依存について。

-アーキテクチャのルールが課すことによる進化の方向付けについて。

コード進化パターン:抽象クラスの継承によるコードの重複の削除 (2)

前回の記事 だけど、分析が足りなかったので補足。

まず、一応 DSM (Design Structure Matrix) で構造変化を表してみる。DSM は要素間の依存関係を表現するための方法。ここでは、要素はクラス単位。

before:
dsm_extends_abstract_class_in_refactoring_before.jpg


after:
dsm_extends_abstract_class_in_refactoring_after.jpg


DSM から分かるように、ConcreteComponentA と AbstractComponent の間に継承の依存関係ができている。


具体的な例のやつも DSM で表してみる。
before:
dsm_extends_abstract_class_in_refactoring_example_before.jpg

after:
dsm_extends_abstract_class_in_refactoring_example_after.jpg


こっちの DSM では、デザインの構造を表す要素 or デザインパラメータであるクラスだけでなく、それらデザインパラメータが依存している「仕様」もデザイン構造の要素して加えている。デザインパラメータとかについては「Design Rules」本(翻訳)」を参照。ソフトウェアデザインでの話は Lopes らの 論文 を参照。

この DSM の例からは、先ほどの例と同じように、ItemUseMenu (ConcreteComponentA) と AbstractMenu (AbstractComponent) の間に継承の依存関係ができている。

依存の要素に「仕様」を含めたので、仕様が変わればそれに伴ってクラスも変更する必要がある(かもしれない)ことが分かる。

たとえば、オープニング(or タイトル画面)で選択するメニューアイテムの数が変化すれば、「オープニングメニュー仕様」が変わる。それに伴って、OpeningMenu も変化する必要がある。同様に、「アイテム使用メニュー仕様」が変われば、ItemUseMenu も変化する必要がある。

一方で「AbstractMenu」は、何の仕様にも依存していないように見える。では、疑問は「AbstractMenu」は、いつ変化するのか、誰(変化のドライバ)によって変化が引き起こされるのか、ということ。

一つは、品質に関する観点。リファクタリングなど。リファクタリングをするか決めるのは誰か。少なくとも、ソフトウェアを使うユーザではない。ユーザは、構造に関係する品質(保守性、モジュラリティ、などなど)を気にしない。ほとんどの場合は、開発者が決める。

図で表すとこんな感じ。
menuitem.png



眠くなってきたのでここで終了。


まとめ:
-DSM (Design Structure Matrix) を使って、コードの変化を確認した。

-デザインの要素だけでなく、仕様などの要求も含めて、DSM で変化の様子を分析した。

課題
-コンサーンとの関係について。

-アーキテクチャのルールについて。

-OpeningMenu/ItemUseMenu の変化が AbstractMenu を変化させるきっかけとなる場合について。

-関連研究の話。Lopes のデザインパラメータの種類。

コード進化パターン:抽象クラスの継承によるコードの重複の削除

コード進化のパターンを考えるシリーズの四回目ぐらい。

本元のドキュメントはこちら。

 Java におけるコード進化パターン

今回は「抽象クラス継承によるコードの重複の削除」という内容。

簡単に言えば、抽象クラス継承すればコードの重複なくせるのでやろう、というリファクタリング。

before:
extends_abstract_class_in_refactoring_before.jpg



public class ConcreteComponentA {

private String name;

public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void methoA() { }
}

public abstract class AbstractComponent {

private String name;

public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

public class ConcreteComponentB extends AbstractComponent {
public void methodB() { }
}



after:
extends_abstract_class_in_refactoring_after.jpg


public abstract class AbstractComponent {

private String name;

public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

public class ConcreteComponentA extends AbstractComponent {

public void methoA() { }
}

public class ConcreteComponentB extends AbstractComponent {
public void methodB() { }
}


このパターンで重要なのは、納期のプレッシャーとかの理由のために、抽象クラスを継承し忘れていた、という条件。なので、before の図では、ConcreteComponentB はすでに AbstractComponent を継承している形になってる。リファクタリング本 の「スーパークラスの抽出」でいえば、AbstractComponent はまだ存在しないし、そのため ConcreteComponentA も ConcreteComponentB も AbstractComponent を継承していない。

もう一つ重要なのは、サブタイピングが進化の動機付けになっていない、という点。抽象クラスを継承する理由は、コードの重複を削除することであって、ConcreteComponentX を AbstractComponent として扱いたいわけではない。やりたいのは、実装の継承であって、型の継承ではない。


以下は、メニューを表現するクラスの具体的な現実の例。
before:

public abstract class AbstractMenu { // AbstractComponent

private int index = 0;
private int menuItemCount;

protected AbstractMenu(int menuItemCount) {
this.menuItemCount = menuItemCount;
}

public void initIndex() {
index = 0;
}

public void nextMenuItem() {

index++;
if (index >= menuItemCount) {
index = 0;
}
}
public void prevMenuItem() {

index--;
if (index <= -1) {
index = menuItemCount - 1;
}
}
public int getSelectedItemIndex() {
return index;
}
}

public class OpeningMenu extends AbstractMenu { // ConcreteComponentB

public OpeningMenu() {
super(3);
}
public boolean isScenarioModeSelected() {
return getSelectedItemIndex() == 0;
}
public boolean isDungeonModeSelected() {
return getSelectedItemIndex() == 1;
}
public boolean isEndGameSelected() {
return getSelectedItemIndex() == 2;
}
}
public class ItemUseMenu { // ConcreteComponentA

private int index = 0;
private int menuItemCount = 2;

public void initMenuIndex() {
index = 0;
}
public boolean isUseMenuSelected() {
return index == 0;
}
public boolean isDeployMenuSelected() {
return index == 1;
}
public void nextMenuItem() {

index++;
if (index >= menuItemCount) {
index = 0;
}
}
public void prevMenuItem() {

index--;
if (index <= -1) {
index = menuItemCount - 1;
}
}
public int getSelectedItemIndex() {
return index;
}
}



after:

public class ItemUseMenu extends AbstractMenu {

public ItemUseMenu() {
super(2);
}
public boolean isUseMenuSelected() {
return getSelectedItemIndex() == 0;
}
public boolean isDeployMenuSelected() {
return getSelectedItemIndex() == 1;
}
}



ここで少し重要なのは、クラスの導入された順序。つまり、OpeningMenu (ConcreteComponentB) は、ItemUseMenu (ConcreteComponentA) の後に導入された。より詳しく言えば、OpeningMenu は ItemUseMenu コピペして作られた。

ここで話はちょっとずれるけど、関連しそうな文献(まだちゃんと読んでない):

Cory Kapser and Michael W. Godfrey

'Cloning Considered Harmful' Considered Harmful

Proc. of the 2006 Working Conference on Reverse Engineering (WCRE-06)

http://plg.uwaterloo.ca/~migod/papers/


つまり、コードの進化の流れから言うとこんなかんじ:
(1)ItemUseMenu (ConcreteComponentA) 追加
(2)OpeningMenu (ConcreteComponentB) 追加
(3)AbstractMenu (AbstractComponent) 追加
(4)OpeningMenu が AbstractMenu を継承
(x)その後、納期の締切りなどの理由のためリファクタリングせず放置
(x+1)ItemUseMenu が AbstractMenu を継承

Effective Java Exceptions

記事メモ。

 Effective Java Exceptions
One of the most important architectural decisions a Java developer can make is how to use the Java exception model. Java exceptions have been the subject of considerable debate in the community. Some have argued that checked exceptions in the Java language are an experiment that failed. This article argues that the fault does not lie with the Java model, but with Java library designers who failed to acknowledge the two basic causes of method failure. It advocates a way of thinking about the nature of exceptional conditions and describes design patterns that will help your design. Finally, it discusses exception handling as a crosscutting concern in the Aspect Oriented Programming model. Java exceptions are a great benefit when they are used correctly. This article will help you do that.

コード進化パターン:Data Manager クラスへの setter/getter の追加

「コード進化のパターン」を考えるパート何だっけ、3ぐらいか。

本ドキュメントは「Java におけるコード進化パターン」にあります。

今回追加したのは「Data Manager クラスへの setter/getter の追加」というパターン。

簡単に言えば、Data Manager micro pattern のクラスに setter/getter のメソッドを同時に追加するような進化のパターン。

Data Manager micro pattern っていうのは、Maman らが提案してる micro pattern のカタログの中の一つ(日本語版の翻訳は こちら)。

micro pattern は、デザインパターンよりも抽象度が低くて、デザインパターンと違って機械的に検出できるようなクラスや interface に対して、構造的な特徴の観点から分類したもの。たとえば、Data Manager は、getter と setter のみのメソッドから構成されるクラスのこと。他にも、public メソッドを一つだけもつクラスを表す Function Pointer など、(論文出版時点で)全部で 27 個ある。


今回の進化パターンでは、この Data Manager を対象としてその進化のパターンの一つを取り上げたい。進化は簡単で、setter/getter のメソッドを Data Manager に同時に追加するケース。以下は進化前と後のクラス図。

before:
add_getter_and_setter_to_data_manager_before.jpg


after:
add_getter_and_setter_to_data_manager_after.jpg


簡単で、単に data3 フィールドを追加という感じ。


もちろん、この進化パターンは、実例からのものを参考にしています。具体的は、次のような Status クラス:
Before:

public class Status {

private int hp = 20;
private int maxHP = 20;

private int str = 5;
private int def = 2;

private int hitRate = 100; // 命中率
private int avoidRate = 5; // 回避率

public int getHP() {
return hp;
}

public void setHP(int hp) {
this.hp = hp;

if (hp > getMaxHP()) {
this.hp = getMaxHP();
}
if (hp < 0) {
this.hp = 0;
}
}

public int getMaxHP() {
return maxHP;
}

public void setMaxHP(int maxhp) {
this.maxHP = maxhp;
}
// ... その他 getter/setter
}


追加したのは、クリティカル率を表す criticalRate フィールド。
after:

public class Status {

private int hp = 20;
private int maxHP = 20;

private int str = 5;
private int def = 2;

private int hitRate = 100; // 命中率
private int avoidRate = 5; // 回避率
private int criticalRate = 5; // クリティカル率

public int getHP() {
return hp;
}

public void setHP(int hp) {
this.hp = hp;

if (hp > getMaxHP()) {
this.hp = getMaxHP();
}
if (hp < 0) {
this.hp = 0;
}
}

public int getMaxHP() {
return maxHP;
}

public void setMaxHP(int maxhp) {
this.maxHP = maxhp;
}
// ... その他 getter/setter

public int getCriticalRate() {
return criticalRate;
}

public void setCriticalRate(int criticalRate) {
this.criticalRate = criticalRate;
}

}


ここでいくつか疑問があると思う。

(1)回避率は、avoidRate なのか。 まあこれは冗談。ただ、普通ゲームで言うところの回避率を英語でなんというか知ってる人は教えてください。

(2)setHP メソッド内のロジックの存在。これはちょっと迷うところ。このようなロジックがある場合、setter と呼んでしまっていいのかどうかという点のため。また、それに関連して Data Manager と呼んでしまっていいのか、という点。これらの点に関しては、アスペクト指向の観点も含めて別の記事で分析したいと思う。


ちなみにこの「Data Manager クラスへの setter/getter の追加」の進化パターンは、次のようなパターン階層に位置している(と考える)。
evo_relation_add_getter_and_setter_to_data_manager.jpg


「メソッドの追加」の進化パターンの全体階層は、今のところこんな感じ。
evo_relation_add_method2.jpg



話は少し変えて、この「Data Manager クラスへの setter/getter の追加」の進化パターンに至る進化のシナリオについて考えるのも、この研究の一部。つまり、この進化パターンは何の後に起こるのか。あるいは、この後にどんな進化パターンが起こるのか、という調査。

この後に何が起こるのかはまだ分析してない(or 実践で出会ってないので分析できない)けれど、前に何が起こっていたのかについての可能性は少し分かっている。候補は二つある(詳しくはリンク先を参照)。
(1)「Data Manager クラスの追加
(2)「パラメータからの Data Manager クラスの抽出

一つ目は、何も無い状態から新たに Data Manager クラスを追加するようなケース。たとえば、機能追加時に新たに追加されるようなケース。

二つ目は、リファクタリング時に、メソッドのパラメータから抽出されたクラスが Data Manager だったというケース。"メソッドのパラメータから抽出" ってのは、たとえば、メソッドのパラメータ数が多かったので、まとめて一つのクラスにする、というようなケース。


なので、とにかく「Data Manager クラスへの setter/getter の追加」が起こるためには、すでに Data Manager が存在していなければならない。上記の二つは Data Manager を導入する進化パターンの例といえる。

進化のパス(or シナリオ)を図で書くなら、次のようになる。
evo_path_add_getter_and_setter_to_data_manager_blog.jpg


しかし、僕は、「コード進化のパターンのカタログ」の中には、この図は入れないことにした。なぜか。それは、例で挙げた Status クラスがその二つのどちらにも当てはまらなかったから。

では、 Status クラスはどこから来たのか。元々は、次のようなクラスがあった。
before:

public class Suiseiseki implements Player, Animatable {
// その他のフィールド

private int lv = 1;
private int exp = 0;

private int hp = 20;
private int maxHP = 20;

// その他のメソッド
public int getHP() {
return hp;
}

public void setHP(int hp) {
this.hp = hp;

if (hp > getMaxHP()) {
this.hp = getMaxHP();
}
if (hp < 0) {
this.hp = 0;
}
}

public int getMaxHP() {
return maxHP;
}

public void setMaxHP(int maxhp) {
this.maxHP = maxhp;
}

// ... その他のメソッド。その他の getter/setter
}

そしてステータスに関係するフィールドが増えてきたので、Status クラスを作ってリファクタリングすることにした。
after:

public class Suiseiseki implements Player, Animatable {

// その他のフィールド

private int lv = 1;
private int exp = 0;

private Status status = new Status();

private int hp = 20;
private int maxHP = 20;

// その他のメソッド
public int getHP() {
return status.getHP();
}

public void setHP(int hp) {
status.setHP(hp);
}

public int getMaxHP() {
return status.getMaxHP();
}

public void setMaxHP(int maxhp) {
status.setMaxHP(maxhp);
}

// ... その他のメソッド。その他の getter/setter
}


元々のリファクタリングの目的は、Suiseiseki クラス(このクラスプレイヤーを表す)から、ステータスに関連するメソッドとフィールドを別のクラスである Status クラスに移動させることで、Suiseiki クラスと Player interface のインタフェースをシンプルにすることだった。

けれど、変更後のコードから分かるように、実際には、色々な都合のため Status クラスへフォワーディングするだけのコード変化になってしまった。


ここで再び Data Manager である Status クラスがどのようにして出現したのかを考えてみたいと思う。候補は二つあった:
(1)「Data Manager クラスの追加
(2)「パラメータからの Data Manager クラスの抽出

一番ではない。Status クラスは、既にあるクラス(Suiseiseki クラス)から抽出された。

二番目でもない。Status クラスは、既にあるクラスが持っていたフィールドとメソッドを移動させるために導入された。

これらの理由から、「Data Manager クラスへの setter/getter の追加」という進化パターンが「Data Manager クラスの追加」あるいは「パラメータからの Data Manager クラスの抽出」の後に起こるという進化のパスが存在するとは、(現状では)いえないと判断したのだった。


今回の話は以上で終わり。だけど、一つ関係することとして面白そうな調査内容を。

同じ Data Manager クラスといえども、元々どのような進化のパスを通して出現したのかによって、後の進化パスは異なるのではないか、という点。

たとえば、メソッドのパラメータから抽出された Data Manager クラスと、あるクラスのフィールドから抽出された Data Manager クラスとでは、この後に同じような進化のパスを通るのだろうか?

これに回答するには、エンピリカルな実験が必要だろうけど、直感としては、メソッドのパラメータから抽出されたような Data Manager クラスは、その後「Data Manager クラスへの setter/getter の追加」みたいな進化のパスを通りにくいんじゃないかと思う。

それが正しいとしたら、次の疑問は、では、それはなぜか、という点。


以上。

Java Plug-in Framework (JPF) Project

単なるメモ。

Java Plug-in Framework (JPF) Project
JPF provides a runtime engine that dynamically discovers and loads "plug-ins". A plug-in is a structured component that describes itself to JPF using a "manifest". JPF maintains a registry of available plug-ins and the functions they provide (via extension points and extensions).

コード進化: EnumMap

「コード変化・進化を考える」のパート2。

今回は、HashMap から EnumMap への進化を例に考えたい。

Java 5.0 からは、列挙を 言語レベルでサポート してくれるようになった。

たとえば、今作ってる ゲーム では、次のような列挙を使って、ゲームのモードを表している。

public enum GameMode {
OPENING,
NORMAL,
// ... その他
GAME_OVER
}


ゲームの設計方法として適切かどうかは微妙だけど、この GameMode にあわせて、ゲーム画面の描画やユーザからのキー入力の扱いを変化させている。たとえば、そのために、GameMode をキーとして使い、値として、キー入力を扱うオブジェクトを Map で登録するようなコードを書いていた。

public class MainPanel extends JPanel {
...

private Map<GameMode, GameModeKeyHandler> keyHandlers =
new HashMap<GameMode, GameModeKeyHandler>();

private void initKeyHandlers() {
keyHandlers.put(GameMode.OPENING, new OpeningModeKeyHandler(openingMenu));
keyHandlers.put(GameMode.NORMAL, new NormalModeKeyHandler(itemMenu));
// ... その他
keyHandlers.put(GameMode.GAME_OVER, new GameOverModeKeyHandler(gameOverMenu));
}
}



で、ここからが本題だけど、昨日気付いたのだけど、enum を Map のキーに使う場合には、EnumMap を使うのが性能的に望ましいということが分かった。

なので上記のコードを HashMap から EnumMap を使うように変更した。

    private Map<GameMode, GameModeKeyHandler> keyHandlers =
new EnumMap<GameMode, GameModeKeyHandler>(GameMode.class);




さて、簡単な例だったけれど、コード変化・進化の観点から軽く分析してみようと思う。

・なぜコードの変化があったか。
-開発者(この場合は僕)がよりよいコーディングについて知らなかったから。性能が出ることが要求されているから。
一般的には、3つのケースが考えられる。
(1)変化がない:開発者がコーディングについて適切な知識を持っていたとすると、HashMap から EnumMap への変化は起こらない。初めから EnumMap を使う。

(2a)変化がある:開発者がコーディングについて適切な知識を持っていなかったので、HashMap から EnumMap への変化が起こる。

(2b)変化がある:「HashMap から EnumMap」の例では当てはまらないけれど、もう一つ考えられる。たとえば、enum の言語機構と EnumMap の導入が同時期でなかった場合。たとえば enum が 5.0 で導入されて EnumMap が 6.0 で導入されると仮定すると、6.0 が出るまでは、開発者は、HashMap を使うことになる。その後、6.0 が出た後に、HashMap から EnumMap へのコード変化が起こる。

・誰がコードの変化を引き起こしたか(開発者? ユーザの要求変化? 環境変化?)。
-開発者。
ただし、微妙ではある。ゲーム開発におけるユーザの暗黙の要求として、FPS (frame per second) が 60 を保つ、というのがある。疑問は「もし、パフォーマンスチューニングをしなくても性能が十分出ていると仮定するなら、HashMap から EnumMap への変化は起こりえたか」という点。

図で書くと以下のような感じ。
hash_enum.png

現実的な観点からみると、パフォーマンスチューニングなしに、大きなゴールである「できるかぎり多くのユーザに遊んでもらいたい」を満たすことはできない。そのため、性能向上は、常に要求される。

・コードの変化により何が変わったか。
-(+)性能向上
-(-)特になし

・デザインの変化・進化といえるか。
-恐らく言えない。クラス内のメソッドやフィールドのみに影響する変更であり、クラス間の構造的な関係を変化されるものではないため。ただし、HashMap の依存から EnumMap への依存の変化はある。


最終的に図で書くとこんな感じか。
hash_enum2.png

コード進化

コード変化・進化の理由を考えていきたいと思う。

簡単なのからいこう。

たとえば、ユーザインタフェースの調整や変更。ボタンや文字の配置場所に伴うコードの変更。

具体例:

public class DungeonSelectionViewDrawer {
...
public void draw(Graphics g) {
...
g.fillRect(baseX, baseY + 10, 100, 3); // 位置などの調整
...
}
}


変更の影響範囲としては:

  • メソッド内のみのコードの変更
  • クラス内のみのコードの変更
  • クラス外へのコードの変更

が考えられる。

一番目は、例で言えば、draw メソッド内のみでのコードの変更。

二番目は、例で言えば、draw メソッド内のコードをリファクタリングして、private メソッドを抽出するような変更:

public class DungeonSelectionViewDrawer {
...
public void draw(Graphics g) {
...
drawLine(g);
...
}
private void drawLine(Graphics g) {
g.fillRect(baseX, baseY + 10, 100, 3); // 位置などの調整
}

}


三つめは、例で言えば、よく使いそうな汎用的な描画コードをユーティリティクラスへ移動させるような変更:

public class GraphicUtils {
...
public static void drawLine(Graphics g, int x, int y, int w) {
g.fillRect(x, y, w, 3);
}

...
}
public class DungeonSelectionViewDrawer {
...
public void draw(Graphics g) {
...
GraphicUtils.drawLine(g, baseX, baseY + 10, 100);
...
}
}

Groovy

いつのまにか Groovy 1.0 がリリースされてたか。

ついでに Groovy 本 とかも、そろそろ発売か。

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。