asatoの技術的な日常日記

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

スポンサーサイト

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

ソフトウェア進化:進化ユニット

更新履歴
2008.02.20:図を追加。
2008.02.20:分かりにくい部分を少し修正。

はじめに


要求仕様の変更や保守性の向上など品質特性の変更に対応するために、ソフトウェアの変更が必要な場合があります。通常、以下の理由のため、ソフトウェアの変更は集団的、つまり連続的な変更です。

 (1)仕様変更や品質特性の変化の粒度の単位が、プログラムの粒度の単位と一対一で対応することは少ない。

req_and_program_gra2.png


 (2)ソフトウェアを構成する要素は、独立ではなく互いに依存しあっているため、ある要素の変更は他の要素の変更を要求する。
program_depend2.png


ここでは、要素間の依存関係に伴う変化を考えます。

明示的な依存関係に伴う変化:1つは、プログラム要素間に形式的な依存関係がある場合です。その場合、依存されている側の要素を変更すると、その要素に依存している要素にも変更が及びます。たとえば、変数名を変更すると、その変数を参照しているコード要素も変更が必要です。変更が要求されるかどうかは、コンパイラやインタプリタ(もしくは依存関係を形式的に定義し、検証できるシステム)がエラーを出すかどうかで分かります。

特徴は、依存関係の定義は、形式的かつ客観的であるため、どんな変更が必要なのかがわかることです。

暗黙的な依存関係に伴う変化:プログラム要素間に明示的な依存関係がないのにもかからわず、あるプログラム要素を変更するとき、その変更に伴って他の要素を変更する場合があります。この種の変化を、暗黙的な依存関係に伴う変化と呼ぶことにします。

この記事では、暗黙的な依存関係に伴う変化に対して考察してみます。特に、暗黙的な依存関係に伴って変化するコード群を、ここでは 進化ユニットと呼びたいと思います。この進化ユニットの観点から考察します。

進化ユニットの定義は今のところ曖昧です。ユニットという言葉が適切かどうかもわかりません。しかし、設計者もしくはプログラマーがなぜこの種の変更を行うのかを原理などの観点から適切に説明するのは困難であると感じています。

実例



実例を紹介します。次のようなコードがあるとします。ゲーム画面を描画するコードです。

コード状態A:


public class StatusViewDrawer implements GameViewDrawer {

// フィールド
public void draw(Graphics g) {
// .. コード

// ここから、
drawStringWithShadow(g, "武器  ", 250, baseY + 30);
if (player.getStrItem() instanceof NullStrItem == false) {
drawStringWithShadow(g, strItem.getName(), 360, baseY + 30);
}

drawStringWithShadow(g, "防具  ", 250, baseY + 60);
if (player.getDefItem() instanceof NullDefItem == false) {
drawStringWithShadow(g, defItem.getName(), 360, baseY + 60);
}

drawStringWithShadow(g, "人工精霊", 250, baseY + 90);
if (player.getSpiritItem() instanceof NullSpiritItem == false) {
drawStringWithShadow(g, spiritItem.getName(), 360, baseY + 90);
}
// ここまでが進化ユニット

// .. コード&その他のメソッド
}
}

public class NullStrItem extends StrItem { // Null Object

// ... その他のメソッド

@Override
public int getStrPoint() {
return 0;
}
}

public class NullDefItem extends DefItem {
// ... 省略
}

public class NullSpiritItem extends SpiritItem {
// ... 省略
}



次の図は、上記のコードによって描画されたゲーム画面を表します。
suisei_status_view2.png


NullStrItem といったクラス名から想像できるように、このコードでは Null Object パターンが適用されています。 ここで、Null Object パターンが適用されているのに、あるオブジェクトが Null Object かどうかを判別するコードがあるのは良くない兆候だと考えたとします(実際にこれが適切な判断かどうかは後の議論に関係ありません)。



if (player.getStrItem() instanceof NullStrItem == false) { // Null Object かどうか
drawStringWithShadow(g, strItem.getName(), 360, baseY + 30);
}



そのため、次のようにコードを変更することにしました。

コード状態B:


public class StatusViewDrawer implements GameViewDrawer {

// フィールド
public void draw(Graphics g) {
// .. コード
// ここから、

drawStringWithShadow(g, "武器  ", 250, baseY + 30);
drawStringWithShadow(g, strItem.getName(), 360, baseY + 30);

drawStringWithShadow(g, "防具  ", 250, baseY + 60);
drawStringWithShadow(g, defItem.getName(), 360, baseY + 60);

drawStringWithShadow(g, "人工精霊", 250, baseY + 90);
drawStringWithShadow(g, spiritItem.getName(), 360, baseY + 90);

// ここまでが変更された

// .. コード&その他のメソッド
}

}

public class NullStrItem extends StrItem {
// ... 他のメソッド
@Override
public int getStrPoint() {
return 0;
}

@Override
public String getName() {
return "";
}

}

public class NullDefItem extends DefItem { // 同じように getName を追加
// ... 省略
}

public class NullSpiritItem extends SpiritItem { // 同じように getName を追加
// ... 省略
}



code_state_a_b.png


コード状態AからBへの変更の過程は上の図に示しめしている通りです。メソッドレベルの変更粒度で見た場合、変更過程は次のようになります。ただし、順番は考慮しません。

 (変更a) NullStrItemクラスへgetNameメソッドを追加

 (変更b) NullDefItemクラスへgetNameメソッドを追加

 (変更c) NullSpiritItemクラスへgetNameメソッドを追加

 (変更d) StatusViewDrawerクラスのdrawメソッドを変更


疑問



ここでの疑問は、なぜこのような変更が起こったのか、です。このような変更過程を引き起こす要因は何か、ということです。たとえば、NullStrItem クラスに getName を追加したからといってなぜその他の NullDefItem と NullSpiritItem も変更したのでしょうか?

この集団的な変更は、明示的な依存関係によって発生したものではないため、変更する人によっては異なる変更結果となりえます。たとえば、NullStrItem クラスにだけ getName を追加し、描画のコードも次のようにだけ変更される可能性もあります。

コード状態C:


public class StatusViewDrawer implements GameViewDrawer {

// フィールド
public void draw(Graphics g) {
// .. コード
// ここから、
drawStringWithShadow(g, "武器  ", 250, baseY + 30);
drawStringWithShadow(g, strItem.getName(), 360, baseY + 30);
// ここまでが変更された


drawStringWithShadow(g, "防具  ", 250, baseY + 60);
if (player.getDefItem() instanceof NullDefItem == false) {
drawStringWithShadow(g, defItem.getName(), 360, baseY + 60);
}

drawStringWithShadow(g, "人工精霊", 250, baseY + 90);
if (player.getSpiritItem() instanceof NullSpiritItem == false) {
drawStringWithShadow(g, spiritItem.getName(), 360, baseY + 90);
}
// .. コード&その他のメソッド
}
}

public class NullStrItem extends StrItem {
// ... 他のメソッド
@Override
public int getStrPoint() {
return 0;
}

@Override
public String getName() {
return "";
}

}



code_state_a_b_c.png



考察



NullStrItem に対して、getName をオーバーライドして定義しましたが、そのメソッドの導入が次のコードの変更を示唆することは分かりません。

変更前:

    drawStringWithShadow(g, "武器  ", 250, baseY + 30);    

if (player.getStrItem() instanceof NullStrItem == false) {
drawStringWithShadow(g, strItem.getName(), 360, baseY + 30);
}



変更後:

   drawStringWithShadow(g, "武器  ", 250, baseY + 30);
drawStringWithShadow(g, strItem.getName(), 360, baseY + 30);


また、NullStrItem に対する、getName メソッドの追加が、NullDefItem への getName の追加を示唆することは分かりません。

示唆がないのに、どうしてこのような変更を行うのでしょうか?


たとえば、次のようなシナリオを考えます。上記のようにコードを変更しようと思ったプログラマAと、変更の一部をまかされたプログラマBがいると考えてください。

次に、プログラマAが、次のコード状態Xから、コード状態Yに変更したとします。目標となるコード状態はコード状態Bだとします。
コード状態X:


public class StatusViewDrawer implements GameViewDrawer {

// フィールド
public void draw(Graphics g) {
// .. コード

drawStringWithShadow(g, "武器  ", 250, baseY + 30);

if (player.getStrItem() instanceof NullStrItem == false) {
drawStringWithShadow(g, strItem.getName(), 360, baseY + 30);
}

drawStringWithShadow(g, "防具  ", 250, baseY + 60);
if (player.getDefItem() instanceof NullDefItem == false) {
drawStringWithShadow(g, defItem.getName(), 360, baseY + 60);
}

drawStringWithShadow(g, "人工精霊", 250, baseY + 90);
if (player.getSpiritItem() instanceof NullSpiritItem == false) {
drawStringWithShadow(g, spiritItem.getName(), 360, baseY + 90);
}

// .. コード&その他のメソッド
}
}

public class NullStrItem extends StrItem { // Null Object

// ... その他のメソッド

@Override
public int getStrPoint() {
return 0;
}
}




コード状態Y:


public class StatusViewDrawer implements GameViewDrawer {

// フィールド
public void draw(Graphics g) {

// ..その他の コード

drawStringWithShadow(g, "武器  ", 250, baseY + 30);

if (player.getStrItem() instanceof NullStrItem == false) {
drawStringWithShadow(g, strItem.getName(), 360, baseY + 30);
}

drawStringWithShadow(g, "防具  ", 250, baseY + 60);
if (player.getDefItem() instanceof NullDefItem == false) {
drawStringWithShadow(g, defItem.getName(), 360, baseY + 60);
}

drawStringWithShadow(g, "人工精霊", 250, baseY + 90);
if (player.getSpiritItem() instanceof NullSpiritItem == false) {
drawStringWithShadow(g, spiritItem.getName(), 360, baseY + 90);
}
// .. コード&その他のメソッド
}

}

public class NullStrItem extends StrItem {
// ... 他のメソッド
@Override
public int getStrPoint() {
return 0;
}

@Override
public String getName() { // この部分を追加
return "";
}

}



次に、プログラマBはプログラマAと、直接的・間接的に、会話もしくは文書によりコミュニケーションをとっていないとします。

次に、プログラマBにコード状態Yが渡されたとします。プログラマBは、コード状態Bに到達するでしょうか?


分かるのは、プログラマBはプログラマAからの何らかの情報が必要だということです。ここでの疑問は、その情報が形式的にどのような情報なのか、ということです。つまり、単に「このコードをこんな感じにこうやって変更しておいて」というような曖昧なやりとりではないということです。

今のところの仮説としては、以下のものです。

 (仮説1)コードの構造、もしくはより抽象的な設計の構造が存在するとき、その構造には、変更対する変更ルールが存在する。

 (仮説2)プログラム要素は、このような変更ルールとの依存関係がある。

 (仮説3)プログラム要素と変更ルールとの関係が、進化ユニットを形成する。


設計の構造とは、ここでは、デザインパターンレベルの抽象度の構造をさします。上記の例では、Null Object パターンを適用しています。

変更ルール(抽象度の低い場合には、コーディングルール、高い場合には設計ルール)が存在するのは当然だと思われるかもしれません。設計書が必要な理由もそこにあるとも言えます。しかし、次のような基本的なことはそれほど明らかではありません。

 (A)ある(部分的な)設計構造が与えられたとき、どのような変更ルールが存在しうるのか。

 (B)そしてその変更ルールはどのような原理・原則に基づいているのか。

たとえば、あるデザインパターンが適用された設計構造があるとして、その設計構造の変化にはどのような種類があるのでしょうか? 具体的は、たとえば、Factory パターンが適用された構造に対してどのような変化が起こりうるのでしょうか? 1つは、concrete factory の役割を持つクラスの追加です。

変更前:
add_concrete_factory_before.jpg


変更後:
add_concrete_factory_after.jpg


Factory パターンのような良く知られたパターンに対してさえ、どのような変更が存在し、その変更がどのような変更ルールに従っており、そしてまたそれらの変更ルールがどのような原理・原則から発生するのかはわかっていません。


まとめと今後の課題



この記事では、進化のユニット という概念を提案しました。進化ユニットが形成される仮説を議論しました。今後は仮説の検証を行います。

スポンサーサイト

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバックURLはこちら
http://asatohan.blog77.fc2.com/tb.php/98-b6115cd0
この記事にトラックバックする(FC2ブログユーザー)

FC2Ad

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