凝集度・結合度・コナーセンス

# モジュールとは

モジュールとは、より複雑な構造を構築するために使用できる標準化された部品または独立した単位の各集合。

# 凝集度

モジュール内の要素同士の関連度を表す指標。
凝集度は結合度よりも精度の低いメトリクス。

> 凝集度の高いモジュールを分割しようとしても、結合が増えて可読性が低下するだけだ。
by Larry Constatine

上に行けば行くほど、凝集度が高い
* 機能的凝集
* 逐次的凝集
*  通信的凝集
* 手続き的凝集
*  時間的凝集
* 論理的凝集
*  偶発的凝集


以下のモジュール定義AとBどちらの方が正しいだろうか？

![](https://storage.googleapis.com/zenn-user-upload/b04febf7de39-20231014.png)

答えは場合による。

顧客管理がCustomerの知識を必要とするなら、Bのようにモジュールを分離すると操作の実現に高度な結合が生じる可能性がある。顧客管理が肥大化する可能性があるならBの方が好ましい。注文管理が2つしかないならAの方が好ましい。

## LCOM

LCOM(Lack of Cohesion in Methods, メソッドの凝集度の欠如) はモジュールの構造的な凝集度を計測するメトリクス。LCOMとは **フィールドを介して結合されていないメソッド群の総和** 。

###  LCOM初期ver.

$$
LCOM = \begin{cases}
    |P| - |Q| &\text{if } |P| > | Q | \\
   0 &\text{otherwise} 
\end{cases}
$$

あるフィールドにアクセスしていないメソッドがあればPが1つ増え、アクセスしていなければQが1つ減る。

### LCOM 96b

$$
LCOM96b = \dfrac{1}{a}\sum{i=1} ^{a}  \dfrac{m- \mu(A_{j})}{m}
$$

### LCOMの欠陥

LCOMは構造的な凝集度の欠如しか見つけられらない。要素が適切に組み合っているかどうかを論理的に判断することはできない。whyがない。

# 結合度

## Afferent Coupling (求心性結合)

コードアーティファクト(コンポーネント、クラス、関数など)に外部から入力される接続数。

## Efferent Coupling (遠心性結合)

他のコードアーティファクトに出力する接続数。

:::note
aはeの後に現れる。これは出力が入力よりも後であることに対応する。
:::

# 抽象度、不安定度、主系列からの距離

## 抽象度

抽象的なアーティファクト(抽象クラス、インターフェイス)と具体的なアーティファクトの比率のこと。
抽象と実装の比率。

$$
A = \dfrac{\sum m^a}{\sum m^c + \sum m^a}
$$

$m^a$ はモジュールの抽象的な要素(インターフェイス、抽象クラス)、$m^c$ は具体的な要素(非抽象クラス)を表す。

5000行のmain()のみからなるアプリケーションの抽象度 $A = \dfrac{1}{5000}$ となり、ほぼ0である。

## 不安定度(Instability)

不安定度はコードベースの不安定さを判断する。不安定度が高いと結合度の度合いが高く、変更されたときに壊れやすい。遠心性結合と求心性結合の合計に対する遠心性結合の比率である。

$$
I = \dfrac{C^e}{C^e + C^a}
$$

$C^e$ が遠心性結合(依存出力数)、$C^a$ が求心性結合(依存入力数)を表す。

## 主系列からの距離

$$
D = |A+I-1|
$$

$A$ が抽象度, $I$ が不安定度を表す。( $0 < A< 1, 0 < I < 1$ )

![](https://storage.googleapis.com/zenn-user-upload/687dbeb44e0b-20231014.png)


理想的なラインに近いほどバランスが良いクラスと言える。

* 無駄ゾーン : 抽象的すぎて使いにくい。
* 苦痛ゾーン : 具体的すぎてメンテナンスが難しい。

![](https://storage.googleapis.com/zenn-user-upload/dd12b2539a58-20231014.png)

:::note
遠心性結合と求心性結合が定義されたソフトウェアの構造化設計法はオブジェクト指向言語普及以前に書かれたものであり、上に挙げたもの以外の結合も定義されているが、それらは以下のコナーセンスに取って代わっている。
:::

# コナーセンス

コナーセンスは、求心性と遠心性の結合メトリクスを改良し、それらをオブジェクト思考言語向けに再構築したもの。

> システムの全体的な正しさを維持するため、あるコンポーネントの変更が別のコンポーネントの変更を必要とする場合、２つのコンポーネントはコナーセンス(接続)されている。 By Meilir Page-Jones

## 静的なコナーセンス

コードレベルでの結合を表す。

### 名前のコナーセンス(Connascence of Name, CoN)

複数のコンポーネントはエンティティの名前に合意する必要がある。

### 型のコナーセンス(Connascence of Type, CoT)

複数のコンポーネントはエンティティの型に合意する必要がある。

### 意味のコナーセンス(Connascence of Meeting, CoM)

複数のコンポーネントは特定の値の意味に合意する必要がある。

### 位置のコナーセンス(Connascence of Position, CoP)

複数のコンポーネントは値の順序に合意する必要がある。

### アルゴリズムのコナーセンス(Connascence of Algorithm, CoA)

複数のコンポーネントは特定のアルゴリズムに合意する必要がある。

## 動的なコナーセンス

実行時の呼び出しを分析したものを表す。

### 実行順序のコナーセンス(Connascence of Execution, CoE)

複数のコンポーネントを実行する順序は重要である。

### タイミングのコナーセンス(Connascence of Timing, CoT)

複数のコンポーネントを実行するタイミングは重要である。

### 値のコナーセンス(Connascence of Values, CoV)

複数のコンポーネントが同じエンティティを参照しているとき、同時に変更する値の一致が重要である。

### アイデンティティのコナーセンス(Connascence of Identity, CoI)

複数のコンポーネントがあるエンティティの特定のインスタンスに依存している時に発生する。

## コナーセンスの性質

### 強さ

コナーセンスの強さはリファクタリングの容易さを表す。強度が強ければ強いほど、変更コストが上がる。
動的なコナーセンスより静的なコナーセンスの方が良い。

名前 < 型 < 意味 < アルゴリズム < 位置 < 順序 < タイミング < 値 < アイデンティティ

e.g.) 値のコナーセンスを意味のコナーセンスへとリファクタリングすることで、コードを改善できる。

### 位置関係

コナーセンスの位置関係は、コードベース内におけるモジュール同士の距離を計測する。
距離の近いコードは、離れたコードに比べて、より高度で多様なコナーセンスの形をもてる。

### 度合い

コナーセンスの度合いは、その影響の大きさに関連する。モジュール数が少ないほど、コナーセンスの度合いが低く、コードベースへのダメージが小さくなる。

#### Page Jonesの3つのガイドライン

コナーセンスを用いて、システムのモジュールを向上させるためのガイドライン。

1. カプセル化された要素にシステムを分割することで、全体的なコナーセンスの度合いを最小に抑えなければならない。
2. カプセル化の境界を跨ぐコナーセンスの度合いは、いかなるものであれ最小に抑えなければならない。
3. カプセル化の境界内では、コナーセンスの度合いを最大化すること。

#### Jim Weirichの2つのアドバイス

* 度合いのルール : 強いコナーセンスを弱いコナーセンスに変換する。
* 位置関係のルール : ソフトウェア要素間の距離が遠くなるにつれ、より弱いコナーセンスを使用する。

## 結合度とコナーセンス

静的なコナーセンスは遠心性結合と求心性結合の度合いを表す。



# アーキテクチャ特性の計測と統率

## アーキテクチャ特性の制定時の問題

* 形が定まっていない
* 定義がさまざま
* 複合的すぎる

## 運用面の計測

## 構造面の計測

内部のコード品質に関する包括的なメトリクスは存在しないが、いくつかのメトリクスと汎用ツールを使えば、限定的にコード構造の重要な側面のいくつかに対処できる。

### 循環的複雑度(CC)

循環的複雑度とひゃ客観的尺度を提供するために設計されたコードレベルのメトリクス。関数、クラス、アプリケーションレベルでのコードの複雑さを表す尺度。

循環的複雑度は異なる実行経路につながるコードにグラフ理論を適用することで算出される。
ある関数が、条件文を持たない場合、`CC = 1` となる。条件文を持っていれば、`CC = 2` となる。

$$
CC = E - N + 2 (N : ノード, E : エッジ) 
$$

```c: sample1
public void decision (int c1, int c2) {
    if (c1 < 100)
        return 0;
    else if (c1 + c2 > 500)
        return 1;
    else
        return -1;
}
```

sample1のCCは 3 ( 3 - 2 + 2)になる。

他の関数(連結成分)に対する呼び出しの場合、より一般的な指揮として

$$
CC = E - N + 2P (N : ノード, E : エッジ, P : 連結成分数) 
$$

:::note
循環的複雑度はどれくらいが良いのか？
他の要因(複雑なドメインなど)を気にしない限り、10以下であることが推奨されている。人によれば、凝集したよく整理されたコードを示す5以下が好ましい。Javaでは **Crap4J** でCCとコードカバレッジの組み合わせを評価できる。TDDはCCの低い、よく整理された高度な凝集度を持つメソッドの作成を促す。
:::

## 適応度関数

アルゴリズムを誘導する仕組みを適応度関数という。

**アーキテクチャ適応度関数** とはあるアーキテクチャ特性またはアーキテクチャ特性の組み合わせについて、客観的な整合性評価を行うための何らかの仕組み。

適応度関数は、メトリクスや監視、ユニットテスト、カオスエンジニアリングといった既存の検証方法と重なり合う。

### 循環依存

クラスやコンポーネントを安易に相互インポートすると、モジュール性を損なう。
コンポーネントが他のコンポーネントと結合すると、巨大な泥団子(Big Ball of Mud)アンチパターンとなる。

コードレビューで、発見するのは遅すぎるので、コンポーネントの循環を検出する適応度関数を実装する必要がある。

Javaであれば、**JDepend** を使ってパッケージ間の依存関係をチェックし、Javaパッケージの構造に循環関係がある場合にテストを失敗するようにする。

### 主系列からの距離を測る適応度関数

**JDepend**を使って主系列からの距離の閾値を設定し、クラスが範囲外になるとテストを失敗するようにする。

### Arch Unit

JUnitのエコシステムに触発されて作られたJava用のテストフレームワーク。




# アーキテクチャ特性のスコープ
## 結合とコナーセンス

求心性結合や遠心性結合のようなコードレベルの結合メトリクスの多くは、アーキテクチャの分析としてはあまりに細かいレベルでの詳細を明らかにする。

### コナーセンス

システムの全体的な正しさを維持するため、あるコンポーネントの変更が別のコンポーネンちおの変更を必要とする場合、２つのコンポーネントはコナーセントされている。

* 静的なコナーセンス
  * 同期的なもの
  * 非同期的なもの
* 動的なコナーセンス

## アーキテクチャ量子と粒度

アーキテクチャ量子とは、高度な機能的凝集性と同期的なコナーセンスをもつ、独立してデプロイ可能なアーティファクト。以下の要素から構成される。

* 独立してデプロイ可能
* 高度な機能的凝集性
  * コンポーネント設計における凝集とは、含まれるコードが目的に沿ってどれだけ統一されているかということを表す。
* 同期的なコナーセンス : あるアプリケーションコンテキスト、もしくはアーキテクチャ量子を形成する分散サービス間における同期的な呼び出しを意味する。

量子のコナーセンスは動的、静的なコナーセンスとは別の概念である。

# 参考文献

* ソフトウェアアーキテクチャの基礎