最近ちょっとメトリクスまわりでゴソゴソやっているので、オブジェクト指向のソースコードを解析する際によく使われる3つのメトリクスについてちょっとまとめてみました。
これらは輪講なんかで何度も聞いているものの、いざ自分で使おうと思うと意外と定義を覚えていないものですね。ただ、学べば学ぶほどこれは代用特性に過ぎないことがどんどん鮮明になってきて「この値には意味があるんだろうか?」、「値の目安はいくつなんだろうか?」とどんどん懐疑的になってきます。論文や書籍には計測方法は述べられていても、品質を高めるためになすべきことが書かれていないということにも不満が募ります。例えばCyclomatic Complexityを下げるためには、極端に言えば if文 や for文 を削減すれば良いわけですが、本質的に複雑な処理であればそもそも一定数の分岐がないと望む結果をプログラムで得ることは不可能なのです。
ひとまずその議論については、また後日。
以下、調べたことのまとめ。
† McCabeのサイクロマチック数(Cyclomatic Complexity, 循環的複雑度)*1
プログラムの複雑さを表し、値が大きいほど、プログラムは複雑である。
ここで述べられている複雑さは分岐の数のこと。分岐が多いプログラムは複雑であるというのがこのメトリクスの基本的な考え方。プログラムをグラフの一種として捕らえ、プログラム中の分岐/合流点をノード、その他の部分をエッジとしたとき、ノードの数(v)、エッジの数(e)から、e ‐ v + 2 で求めた値。直感的な数え方としては分岐数( if や for など )+1 で求められる。
この算出方法のモデルとなっているのはオイラーの多面体公式であり、プログラムのパスが平面をいくつの面に分割するかということに相当するらしい。"らしい"というのは学生時代に博士課程まで数学を専門としていた某教授が教えてくれたことの受け売りだから。
† Martinのメトリクス(OO Design Quality Metrics)*2
上記の Cyclomatic Complexity は抜け道があり、手続きを細切れにしてやれば見かけ上の数値はいくらでも小さくすることができる。ということで、モジュール(ここではオブジェクト指向なのでクラス)間の関係も考慮したメトリクスが必要だろうということで生まれたのがこのメトリクス。これが生まれた頃、オブジェクト指向は「これさえあればプログラムの再利用できてみんな幸せになれる」と言われていた頃だったのかもしれません。
■Abstractness (A)
A = 抽象クラス数(AC) / 具象クラス(CC) + 抽象クラス数(AC)
パッケージ内のクラスの抽象度。計算された数値は0~1の値を取り、1に近いほど抽象度が高い。
■Afferent Couplings (Ca)
パッケージ内のクラス「に」依存するパッケージ外のクラス数。被依存クラス数。
クラス図を書いたときに外部から自身のパッケージに向かう内向きの矢印の数と考えるとわかりやすい。
例えば、あるクラスに対して仕様変更(例えばメソッドのシグニチャの追加、変更、削除)を行うと、依存するクラスはそれに合わせて書き換える必要が出てくる。このような状況を仕様変更の波及と呼ぶとすると、仕様変更の波及は UML の矢印の逆方向に発生する。これは関連や依存だけに限らず、継承、実現等の矢印の形に関係なく起こる。ここでは依存に注目してみると、被依存クラスが多いほど、自身の仕様変更が他に影響を及ぼしやすいことを表す。
■Efferent Couplings (Ce)
パッケージ内のクラス「が」依存するパッケージ外のクラス数 。依存クラス数。
クラス図を書いたときに自身のパッケージから外向きに出る矢印の数と考えるとわかりやすい。
Ca の時の逆で依存クラスが多いほど、他のクラスからの仕様変更の波及を受けやすいことを表す。
■Instability (I)
I = Ce / (Ce + Ca)
パッケージの不安定性。値は0~1をとり、値が大きいほど不安定。
Ce や Ca は上限値が決まっていないので値は 0 ~ ∞ まで取りうるので扱いづらい。このため Ce と Ca の比を求めることにより、相対的な仕様変更の影響を受けやすさ(不安定性)としている。つまり、不安定とは相対的に他のパッケージのクラスへ依存性する傾向が強いことを示しており、自分の仕様変更の影響は他に伝搬しにくいが、自身は他のクラスの仕様変更の影響を受けやすい。逆に安定とは相対的に他のパッケージのクラスから依存される傾向が強いことを示しており、自分の仕様変更の影響は他に伝搬しやすく、自身は他のクラスの仕様変更の影響を受けにくい。
■Distance from the Main Sequence (D)
D = |A + I - 1|
パッケージの主系列(A + I = 1)からの標準化された距離。
抽象度 A と不安定性 I について別個に値の高低を気にするのではなく、そのバランスに注目しようとするものです。
発案者の基本的な考え方として「抽象化度が高いものは、他からよく使われる(たくさん依存される)べき」で「具象度が高いものは、他からあまり使われることは望ましくない」というのがあるのだろうと思います。これをこれまでに求めた抽象度 A と不安定性 I を使って考えていきます。この2つの値がとる理想状態を考えてみると「 A=1(抽象度が最高), I=0 (不安定性が最低)」もしくは「A=0(抽象度が最低), I=1 (不安定性が最高)」ということになろうかと思います。実際問題としてメトリクスがこのような値を取ることはないので、この2つの状態の間をリニアに補完してもよいと考えると (0,1) と (1,0) を結ぶ直線ができます(下図の赤線)。この線をここでは Main Sequence と呼び、この線上に乗っているとき、抽象度 A と不安定性 I のバランスがとれている状態であると考えます。
逆にバランスの悪さを考えたいときには、A と I の関係がこの線からどの程度離れているか(Distance)を求めればよいことになります。計算された値は 0~1 を取るので、値が 1 に近いほど「そのパッケージは抽象度が高いがあまり利用されていない」か、「抽象度が低いが他からの依存が強い」など、抽象度と依存度のバランスが不適切であることを表しています。
† Chidamber と Kemerer のメトリクス (CKメトリクス)*3
こちらもモジュール間の関連を加味したメトリクス。
モジュール間だけでなく、モジュール内の複雑さも計測しようとしていいるのが違いということになるのかな。
■Depth of Inheritance Tree (DIT)
対象のクラスの継承木中における階層の深さ。
値が大きいほど、階層が深い位置にあることを表す。つまり、祖先クラス(Ancestor)が多ければ、変更の影響を受けやすく、クラスの動作を理解するために、祖先クラスを読み解かなければならないため、属性や操作の可読性も低いことを表す。Javaではjava.lang.Objectを1として、自作のクラスは2、そこからextendするごとに1ずつ増加させるという感じでしょうか。依存に注目していた Ce を継承に置き換えて考えたものと考えるとわかりやすい。
■Number of Children (NOC)
子クラスの数。
DITとは逆に自クラスの変更が子のクラスに与える影響の大きさを表す。依存に注目していた Ca を継承に置き換えて考えたものと考えるとわかりやすい。
■Response for a Class (RFC)
クラスの応答の大きさ。
あるクラスに存在する、メソッド呼び出しの種類の数。値が大きいほど、他のクラスへの依存度が高く、処理も複雑であることを表している。呼び出し先の豊富さみたいなものを見ているので、クラスが多大な責務を抱えていないかの目安になるのかも。
■Coupling between object classes (CBO)
クラスが依存する外部クラスの数。
基本的な考え方はCeと同じ。メソッドの呼び出し先、アクセスしているフィールド、継承、メソッドの引数、戻り値、例外の型の数をカウントする。継承元のクラスや基本型は含まない。
■Weighted methods par class (WMC)
クラスのメソッドの複雑さの合計。
数字が大きいほどクラスが複雑である事を表す。重み付き(Weighted)とあるので、メソッドのサイクロマチック数の総和をとる方法もあるが、重みをつけずにメソッド数をそのまま使う(重みは常に1と考える)方法の方がメジャーらしい。重みをつけない方がメジャーなのはどうしてなのか、その理由については要調査です。
■Lack of cohesion in methods (LCOM)
メソッドの凝集度(の欠如)。数値が大きいほど、クラスに含まれるメソッド間の凝集度は高くなる(ので、元々のイメージと実際に求められる数字がちょっと違うところに注意した方が良いかも)。求め方はクラスに内包される2つのメソッドについて、それぞれアクセスするフィールドの集合を考え、共通する要素が含まれるものの数をQ, 含まれないものの数をPとしたとき、下式で求められる。
LCOM = (P > Q) ? (P - Q) : 0
結局この値はフィールドがメソッド間でどの程度、共通して利用されているかということを表している。クラスに内包されるメソッドの凝集度が高ければ、クラスのフィールドは沢山のメソッドからアクセスされるようにクラスが構成されるはずという考え方や、逆にフィールドを共有しないようなメソッドは責務が別なので、別のクラスに分けたほうが良いと言う考え方に基づいていると思われます。
Object-Oriented Metrics: LCOMによると、LCOMには改良型のLCOM**4というものがあるようです。このページ、Dublin City Universityなのに図表のキャプションがなぜか日本語。。。。
オリジナルの LCOM は上限が決まっていないため、扱いづらいので、これを上手に正規化することをLCOM* は意図したのではないでしょうか。LCOM* は 0~1 で表され、1に近いほどクラスの凝集度が小さく、0に近いほど凝集度が高いことを表しています。僕もこっちの方がオリジナルよりもLCOMの意味を良く表すようになっているように感じます。
このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/3271
コメントは承認後の表示となります。
OpenIDでログインすると、即時に公開されます。
OpenID を使ってログインすることができます。