2011年11月3日木曜日

仲間外れはどれか?

下の図は、仲間外れを見つける問題例だけど、小学校受験のためのものだから幼稚園児向けということになる。

仲間外れといっても、一応、少なくとも野菜ではあるのだから、問題の趣旨を一旦置いておけば、あながち間違った括りとは言えなくもない。

でも、じゃあ、「カボチャ」の代わりに、「八百屋さん」だったり「プログラマ(好きな食べ物:肉、嫌いな食べ物:野菜)」とかだったとしても、同じカテゴリって言い張る奴っているのだろうか。まあ、こんなアホな質問はした事はないけど、身の回りには多分いないと思う。つうか、いたら嫌だ。

ところが、それに相当するようなソースコードなら、プロの開発現場でもしょっちゅう見かける。今日はそんな事を書いてみる。

====

こんなコードがあったとする。

このコードには、ダメなところがいくつかあるけど、特にまずい問題点を、ちゃんと技術者っぽい言葉で説明できるだろうか。

/** 色を指定できるカーペット。 */
public class Carpet {
   private final int color;
   public Carpet(int color) {
      this.color = color;
   } 
   //…
}

/** システムで使える色の集合  */
public class Colors {
   private static final Map COLORS = new HashMap();
   static {
      COLORS.put("赤", 0xFF0000);
      COLORS.put("緑", 0x00FF00);
      COLORS.put("青", 0x0000FF);
   };
   private Colors() {} 
   public static int colorNameToRgbValue(String colorName) {
      return COLORS.get(colorName); 
   }
   public static List createCarpets(String[] colorNames) {
      List result = new ArrayList();
      for (String colorName: colorNames) {
         result.add(new Carpet(COLORS.get(colorName)));
      }
      return result;
   }
}

Colors は本当は enum を使うべきだけど、サンプルの都合上普通のクラスにした。また、そもそもユーティリティクラスがまずいってのは、取り敢えずこの際、放っておく事にする。

Colors という色のセットを表すクラスに、色に関係ない事はないけど明らかに異質でレベルの違う Carpet という概念を扱うメソッド、createCarpets() が定義されている。

こういうとき、ちょっと堅苦しい言い方だけど、「凝集性が低い」という(もちろん低いほどダメ)。

もっと言うと、暗合的凝集度論理的凝集度の間くらいになると思われ、レベルとしてはサイテーに近い。(技術者っぽくない言葉でいうと、暗合的凝集度は「なんで同じクラスに入れてんのかイミフ」なやつで、論理的凝集度は「言い訳できない事ないけど必然性に乏しい」やつ。)

この凝集性と表裏一体の概念として、結合度というのもある。

上のコードは結合度が高い(こっちは高いほどダメ)のだけど、実質的にグローバル関数といえるメソッド群がクラス変数という実質グローバルな情報を介して結びついている点で「共通結合」であり、6段階あるうちの悪い方から2番目で、やはりかなり酷いコードと言える。

さらに、凝集性や結合度はオブジェクト指向隆盛以前の、構造化手法全盛期からある設計評価基準だけど、オブジェクト指向に関連の強い言葉「責務」を用いて表現すれば、「createCarpets によって、Colors が一つ以上の異なる責務を負っている事が問題」と言う事もできる(責務:言うまでもなく CRC の R(Responsibility))。

つうわけで、上のコードは単純過ぎる例ではあるものの、技術的には「低凝集・高結合度で、複数の責務を負った下手糞なコード」だと言える。まあ、そういうコードや設計を放置しておくと、その先どうなるかについて、技術者じゃない人になら説明する必要があるけど、技術者同士なら説明不要だと思う(思いたい)。

ただし、実はクソヘタプログラマだけがこんなコードを書いているのではなくて、そこそこの技術者/チームでも、開発期間の中で生じるいろんな行きがかり上、たまたまこんな感じになってしまって、そのまま直す機会を逸してしまってる場合もある。

まあ、でも。そんな時のためのリファクタリング技術なわけで、ちゃんとテストコードを実行するという前提(当たり前だけど、無ければ必ず書き足す)さえ守れば、なんぼでも体質改善すれば良い。

だけど、そもそもダメだって事が認識できないとなると、いろんな意味でかなりやばい。説明するこっちも、クラスの責務から説明すればいいのか、結合度・凝集度から説明すればいいのか、それとも小学校受験の仲間外れ問題まで遡って説明しなければならないのかと考えてしまって、なんかもうキッツイわ…

0 件のコメント:

コメントを投稿