凝集度
#設計 #ソフトウェアの品質
code:markdown
チートシート
1. 偶発的凝集:無関係な処理がたまたま同じモジュールに置かれている
2. 論理的凝集:論理的に同じカテゴリというだけで集められている(例:全バリデーションを1クラスに)
3. 時間的凝集:同じタイミングで実行されるという理由だけで集められている
4. 手順的凝集:決まった順序で実行される処理が集められている
5. 通信的凝集:同じデータを扱う処理が集められている
6. 逐次的凝集:ある処理の出力が次の処理の入力になる一連の処理が集められている
7. 機能的凝集:単一の明確な機能を遂行するためだけに全要素が存在している
/icons/hr.icon
cohesion
責務の境界は発見するものであり、凝集度はその発見の精度を映す
凝集度はモジュール内部の要素がどれだけ一つの責務に向かってまとまっているかを表す設計指標である。結合度が二者間の依存を外側から観測するのに対し、凝集度は一者の内部状態を問う。結合度はimport文や呼び出し関係から比較的客観的に測定できるが、凝集度は「これらが一つの責務か」という設計者の解釈を不可避的に含む。
凝集度が低いという観察は、複数の責務が同居している可能性を示唆する。ただし完全に無関係な責務の同居よりも、関連はあるが独立に変化する責務の同居のほうが実務では圧倒的に多い。この濃淡を無視して機械的に凝集度を高めると、クラスの細分化によってクラス間の結合が増え全体の複雑性が上がる転倒が起こる。
古典的な分類
構造化設計(Constantine & Yourdon, 1970s)による7段階分類。弱い順に並べる。
1. 偶発的凝集:要素間に論理的関連がない。util.rb や helpers.py が典型例
2. 論理的凝集:「すべてのバリデーション」のように機能の種類でグルーピング。フラグで処理を切り替える構造になりやすく、制御結合と表裏一体
3. 時間的凝集:同じタイミングで実行される処理のまとまり。初期化処理の一括モジュールが典型例
4. 手順的凝集:特定の順番で実行される処理のまとまり。時間的凝集より強いが、各ステップの扱うデータや関心は異なることがある
5. 通信的凝集:同じデータに対する操作のまとまり。
6. 逐次的凝集:出力が次の入力になるパイプライン構造。ETLやミドルウェアチェーンが該当
7. 機能的凝集:全要素が単一の機能遂行のために存在する。「単一の機能」の粒度定義が実務上の論点
1〜3が実務で多く見られる問題領域であり、「とりあえずここに置く」という判断の積み重ねで自然発生する。
古典的分類の限界と現代的な再解釈
古典的分類は関数やモジュールの内部を対象にしていたが、現代ではクラス・パッケージ・サービス・チームといった複数スケールで凝集度を考える必要がある。
ドメイン凝集
DDDの集約やBounded Contextは、技術的分類ではなくビジネス上の関心(「従業員」「組織」)でモジュールを切る。ビジネス上の変更要求はドメイン概念の単位で発生しやすいため、変更がドメイン内で閉じる確率が高くなる。ただし横断的関心事(権限、監査ログ、通知)ではドメイン凝集だけでは設計が完結しない。
レイヤー凝集とフィーチャー凝集
レイヤー凝集(controllers/、services/)は技術的役割でグルーピングする。初期の認知負荷は低いが、一つの機能変更が複数レイヤーにまたがり変更の局所性が低い。フィーチャー凝集(features/employee/)はビジネス機能でグルーピングし、変更の局所性が高いが、フィーチャー間の共通処理の配置が問題になる。
Railsのようなフレームワークではレイヤー凝集が出発点になり、成長につれてPackwerk等でフィーチャー凝集を導入していく進化パスが現実的である。
チーム凝集
チームの認知負荷は有限であり、無関係な複数ドメインを一チームが担当するとコンテキストスイッチングコストが上がり専門性が育ちにくい。Conwayの法則を踏まえると、チームの凝集度が低ければコードの凝集度も偶発的・論理的凝集に陥りやすい。
凝集度が低いとき何が起こるか
以下のコードスメルは凝集度の低下として読み替えられる。
散弾銃手術(Shotgun Surgery):一つの変更が多数のモジュールに波及する。関連責務が分散している
変更発散(Divergent Change):一つのモジュールが複数の異なる理由で変更される。複数責務の混在を直接示す
Feature Envy:メソッドが他クラスのデータを多く参照する。データとロジックが分離し、通信的凝集すら未達成
/icons/hr.icon
ソフトウェアの品質(凝集度)って?? #ソフトウェア工学 - Qiita