メトリクス

この文書では、設計時に気になるいくつかのメトリクスを計算するために必要な情報を収集します。

その大半は、図々しくもWebGain QAマニュアルと、 Robert C. Martinの記事: Object Oriented Design Quality Metric - An Analysisのコピーです。 併せて、McCabe publicationの Structured Testing: A Testing Methodology Using the Cyclomatic Complexity Metricも参照下さい。

V(G) | LOC | DIT | NOA | NRM | NLM | WMC | RFC | DAC | FANOUT | CBO | LCOM | NOC A Ca Ce I Dn

サイクロマティック複雑度 - V(G)

このメトリクスは、制御フローの複雑さや、 サブルーチンのようなモジュールにおける枝分かれの複雑さの量を測定するために、 1970年代に導入されました。 コードが実行される経路の数を表します。 当初は、各経路を実行するモジュールのテストケースを作成するコストのある尺度として開発されました。

高いサイクロマティック複雑度を持つメソッドは、理解や保守が難しくなりがちです。 一般的に、アプリケーションのメソッドが複雑になると、 テストが難しくなるでしょう。 そして、信頼性に悪い影響を与えるでしょう。

V(G)はメソッドやコンストラクタの操作フローの複雑さを表す尺度です。 メソッドの本体の分岐の数を数え、以下のように定義されます:

  • while文
  • if文
  • for文
  • 三項演算子や論理積/論理和はどうでしょう?
このメトリックは、異なる2通りの計算方法があるべきです:
  • MCC (caseを考慮する): それぞれの関数は1の基本的な複雑さを持ちます。 if/do/whileが1つあるごとに1を加えます。 nの分岐先を持つswitchは(n-1)を加えます。
  • MCN (caseを考慮しない): それぞれの関数は1の基本的な複雑さを持ちます。 if/do/whileが1つあるごとに1を加えます。 switchは2を加えます。
通常、10が正常状態の最大値として認められています。

行数 - LOC

おそらく、すべてのメトリクスの中で定義と計算が最も簡単です。 行数を数えることは、ソフトウェアのメトリックとして長い歴史を持ちます。 それは構造化プログラミングが興る前までさかのぼります。 今日でも使い道は多岐にわたります。 メソッドの大きさは理解のしやすさ、再利用性、保守性に影響を及ぼします。 大きさを計算する様々な方法があります。 これらは、コードのすべての行数、文の数、コードの空行の数、コメント行の数、 ブロックのデリミタのような文法要素だけからなる行を含みます。

このメトリックは他の構造物のサイズを測ることにもまた使えます。 例えば、Javaクラスやパッケージ全体のサイズを、ソースの行数を数えることで計測できます。

コンパイル単位(ソースファイル)、クラス、インターフェイス、メソッド、 コンストラクタ、フィールドのサイズを決定するためにLOCを使用できます。 そして、以下を無視できるように設定できるべきです:

  • 空行
  • コメントだけからなる行
  • JavaDocだけからなる行
  • ヘッダ部分だけからなる行(正規表現?)
  • 開き中括弧、閉じ中括弧だけからなる行
通常、1000がクラス/ファイルの最大値として認められています。

継承関係の階級の深さ - DIT

このメトリックはクラスが宣言された継承関係がどれだけ深いか計算します。 Javaではすべてのクラスはjava.lang.Objectを最上位のスーパークラスとして持ちます。 これを深さ1と定義します。 つまり、java.lang.Objectを直接拡張したクラスは2の値を持ちます。 そしてそのサブクラスは3の値を持ちます。 これ以降も同様です。

ツリーの深いところにあるクラスは多くのメソッドと状態変数を継承するため、 複雑になり、振る舞いを予測するのが難しくなります。 継承レイヤが多いシステムを理解することは、とても難しくなりがちです。

クラスやインターフェイスのDITは次のように定義されます:

  • すべてのインターフェイス型は1の深さを持ちます。
  • java.lang.Objectクラスは1の深さを持ちます。
  • 他のすべてのクラスは、そのスーパークラスの深さに1を加えた深さを持ちます。

属性の数 - NOA

クラスの明確な状態変数の数は1つの複雑さの尺度となります。 クラスがより多くの状態を表現すると、不変性を保つことが難しくなります。 また理解と再利用を妨げます。

Javaではprotectフィールドを介してサブクラスに状態を公開できます。 サブクラスも不変性が存在することを知り、維持することを要求されます。 データのカプセル化を阻害すると、欠陥や状態変数間の隠された依存関係の要因となりえます。

NOAはクラスやインターフェイスに対して定義されます。 クラスやインターフェイスで宣言されたフィールドの数を数えます。

リモートメソッドの数 - NRM

NRMはクラスに対して定義されます。リモートメソッドの呼び出しは、 以下のクラスで宣言されていないメソッドの呼び出しとして定義されます:

  • クラス自身
  • そのクラスが拡張したクラスや、実装したインターフェイス
  • そのクラスを拡張したクラスやメソッド
クラスのすべてのメソッドやコンストラクタのリモートメソッドの呼び出しの数が値になります。

ローカルメソッドの数 - NLM

NLMはクラスやインターフェイスに対して定義されます。 ローカルメソッドは、クラスやインターフェイスで宣言されているメソッドとして定義されます。 そのクラスのすべてのスーパークラスのローカルメソッドをNLMに含めるように設定できます。 パラメータの設定次第で、public、protected、パッケージprivate、private の可視性を独立して数えることができます。

クラスごとの重み付きメソッド - WMC

プロジェクトの設計やモデリングのフェイズでクラスのメソッドの数が決定するなら、 その開発、デバッグ、保守にどれだけの時間と作業がかかるかを予測する値として使用できます。 それぞれのメソッドの複雑さの重みを統合することで、 メトリックを更に洗練することができます。 通常はメソッドのサイクロマティック複雑度を重みとします。

あるクラスのサブクラスは、public、protected、 場合によってはパッケージprivateのメソッドもすべて継承します。 そのため、メソッドの数はサブクラスの複雑さに直接影響を与えます。 たくさんのメソッドを持つクラスはしばしば特定のアプリケーションに特化し、 再利用性が下がります。

WMCはNLMを元にして定義されます。そして、 継承したメソッドや可視性の違いを考慮するための同種の設定パラメータを持ちます。 主な違いは、NLMではメソッドをすべて1として数えることに対して、 WMCではメソッドを重み付けすることです。 重み付けは2つの仕組みで行います:

  • V(G)(メソッドのサイクロマティック複雑度)を重みとして使用します。 クラスファイルのメソッドのV(G)の値は1とします。
  • メソッドのパラメータ数であるarityから重みを決定します。

クラスへのレスポンス - RFC

クラスのレスポンスの集合は、 そのクラスのオブジェクトにメッセージが送られた結果実行されるすべてのメソッドの集合です。 これには、クラスの継承関係によるメソッドや、 他のオブジェクトに対して実行されるメソッドも含みます。 クラスへのレスポンスのメトリックはクラスへのレスポンス集合の大きさとして定義されます。 より大きなレスポンス集合を持つクラスは、 小さなレスポンス集合のクラスより複雑と考えられます。

理由の1つは、クラスのメソッドを呼び出した結果、 そのオブジェクトや他のクラスの多くの異なるメソッド呼び出しが発生する場合、 そのクラスの振る舞いをテストし、問題をデバッグすることがより難しくなりえることです。 クラスのオブジェクトがシステムの残りの部分と持ちうる潜在的な相互作用のより深い理解を要求されることが多くなります。

RFCはクラスに対するNLMとNRMの合計として定義されます。 すべてのpublic、protected、パッケージprivate、privateメソッドを含みます。 スーパークラスだけで宣言されたメソッドは除きます。

データ抽象カップリング - DAC

DACはクラスやインターフェイスに対して定義されます。 クラスやインターフェイスに宣言されたフィールドで参照される型の数が値となります。 配列のコンポーネント型も数えます。 そのクラスのスーパータイプやサブタイプとなる型のフィールドは数えません。

ファンアウト - FANOUT

FANOUTはクラスやインターフェイス、コンストラクタ、メソッドに対して定義されます。 以下で使用される参照型の数を数えます:

  • フィールド宣言
  • パラメータや戻り値の型
  • throws宣言
  • ローカル変数
配列のコンポーネント型も数えます。 そのクラスのスーパータイプやサブタイプとなる型は数えません。

オブジェクト間の対 - CBO

あるオブジェクトやクラスが他のオブジェクトやクラスを使用するとき、 対になっているといわれます。 対の主な要因の1つはスーパークラスとサブクラスの関係です。 また、他のクラスのメソッドやフィールドにアクセスするとき、 メソッド実行時に他のクラスのオブジェクトが渡されるとき、 返されるときも、対をなします。 オブジェクト間の対は、2つのオブジェクト間の継承関係を持たない対の尺度です。

対の値が大きいと、クラスのモジュール性が下がり、再利用が難しくなります。 クラスの独立性が高まれば、システムの他の部分で再利用できる可能性が高くなります。 クラスが他のクラスと対になれば、そのクラスの変更の影響を受けるようになり、 結果保守が難しくなります。 更に、他のクラスに過剰に依存したクラスは理解や単体テストが難しくなります。

CBOはクラスやインターフェイス、コンストラクタ、メソッドに対して定義されます。 以下で使用される参照型の数を数えます:

  • フィールド宣言
  • パラメータや戻り値の型
  • throws宣言
  • ローカル変数
また、以下についても数えます:
  • フィールドやメソッドの選択が行われた型
配列のコンポーネント型も数えます。 そのクラスのスーパータイプやサブタイプとなる型は数えません。

メソッドの凝集の欠如 - LCOM

クラスの凝集とは、クラスのメソッドがお互いに関連している程度です。 メソッドの集合の中で状態変数にアクセスするパターンを調査することで決定します。 もし、すべてのメソッドが同一の状態変数にアクセスするなら、高い凝集度を持ちます。 もしアクセスする変数の集合が互いに素なら、凝集度は低くなります。 凝集度が低い究極の例は、状態変数にアクセスするメソッドが存在しない場合でしょう。

クラスのメソッド凝集度が低い場合、クラスの設計でおそらく正しく分割されなかったことを示しています。 そして、個々の凝集度が高い複数のクラスに分割することが有益です。 一方で、高い凝集度(凝集の欠如がほとんどない)はそのクラスが上手く設計されていることを暗示します。 凝集度の欠如はカプセル化の度合いを下げ、複雑さを増すのに対して、 凝集度の高いクラスはカプセル化の度合いが高くなることが多いでしょう。

Javaプログラムにとって役に立つその他の凝集の形式に、 ネストしたクラスとエンクロージングクラスの間の凝集があります。 エンクロージングクラスとの凝集がとても低いネストしたクラスは、 ネストしたクラスよりピアクラスとして設計する方がよりよいでしょう。

LCOMはクラスに対して定義されます。 機能的に、そのクラスのメソッドのそれぞれの対を取り、 それぞれがアクセスするフィールドの集合を決定します。 もし、アクセスするフィールドの集合が互いに素な場合Pを1つ増やします。 少なくとも1つのフィールドアクセスを共有すれば、Qを1つ増やします。 それぞれのメソッドの対に計算した後、LCOMを計算します。 LCOM = (P > Q) ? (P - Q) : 0 (訳註:Lack of Cohesionなので、この値が小さいと凝集度が高くなります)

ローカルメソッドを介したフィールドへの間接的なアクセスは、 メトリックの設定パラメータによって考慮に入れることができます。

クラス数 - NOC

システム全体のサイズは、含まれるクラスの数を計算することで見積もることができます。 より多くのクラスを持つ大きなシステムは、小さなシステムよりも複雑になります。 オブジェクト間で起こりうる相互作用の数が大きくなるためです。 このことはシステムの理解性を下げ、その結果テスト、デバッグ、保守が難しくなります。

プロジェクトの初期設計フェーズ中にシステムのクラス数を計画できるなら、 システムを開発、デバッグ、保守するためにかかる時間と作業を見積もる根拠として使用できます。

システム全体と同じように、パッケージやクラスのレベルに対してもNOCメトリックを有効に適用できます。

NOCL (訳註:NOCの間違いだと思われます) はクラスやインターフェイスに対して定義されます。 宣言されるクラスやインターフェイスの数を数えます。 通常1はとなりますが、ネストしたクラス宣言があるとこの値が増えます。

抽象性 - A

A = 抽象クラス数 % 全クラス数

このメトリックは[0,1]の範囲の値を取ります。 0は完全に具象であることを意味し、1は完全に抽象的であることを意味します。

球心性の対 - Ca

このカテゴリ内のクラスに依存する、カテゴリ外にあるクラスの数です。

遠心性の対

カテゴリ外のクラスに依存する、カテゴリ内にあるクラスの数です。

不安定性 - I

I = Ce / (Ca + Ce): このメトリックは[0,1]の範囲の値を取ります。 0は最大限に安定したカテゴリを示します。 1は最も不安定なカテゴリであることを示します。

メインのシーケンスからの正規化された距離 - Dn

Dn = | A + I - 1) | (訳註:“| A + I - 1 |”の間違いと考えます) メインシーケンスからカテゴリまでの垂直距離です。 このメトリックは[0,1]の範囲の値を取ります。 0近傍の値を取らないカテゴリは、より再利用可能で変更の影響を受け難いカテゴリを定義するため、 再検討し再構成することができます。