プログラミング初心者がアーキテクトっぽく語る

見苦しい記事も多数あるとは思いますが訂正しつつブログと共に成長していければと思います

コードの構造化 - コードの結合

前回はコードをコンポーネントに分割することについて学んだ。

分割されたコンポーネントは単体では意味をなさない。コードをコンポーネントに分割した後はコンポーネント同士を組み合わせる必要がある。

組み合わせ方にもよいやり方と悪いやり方がある。悪いやり方をするとコンポーネント同士でスパゲティ化する。スパゲティの単位がコードからコンポーネントになっただけで本質的な問題は解決していないことになる。

ではどのようにコンポーネント同士を組み合わせればよいのだろう。その指針となるのが以下の2つだ。

  1. 依存関係(Dependency
  2. 結合度(Coupling)

先に結論を言ってしまうとコンポーネント間の依存関係はすっきりしている方が好ましく、コンポーネント間の結合度は密より疎であることが好ましい。

詳しく見ていこう。


依存関係

コンポーネントAがコンポーネントAを呼び出すとき、コンポーネントAはコンポーネントBに依存しているという。依存関係が相互に入り乱れるとスパゲッティ化する。よい依存関係を築く必要がある。

原則として依存関係はすっきりしている方が好ましい。また変更が少ないコンポーネントに依存することで変更の影響を受けにくくすることができる。

よい依存関係を設計するための原則を以下に示す。

1. 非循環式依存関係の原則

  • Acyclic Dependencies Principle
  • 依存関係を1方向にすること
  • 特定のアーキテクチャでは双方向の依存関係が発生しやすい(マイクロサービスアーキテクチャなど)
  • どうしても双方向になってしまう場合はDependency Inversionを利用して緩和する
    • DI:適切なInterfaceをコンポーネント間に挿入することで依存関係を弱くすること

2. 安定依存の原則

  • Stable Dependencies Principle
  • 変更される可能性が少ないコンポーネントに依存すること

3. 安定度・抽象度等価の原則

  • Stable Abstractions Principle
  • 抽象度と安定度は同程度にすること
  • 言い換えると

結合度

結合度とは簡単に言えばあるデータが利用される範囲だ。範囲が狭ければ変更の影響が狭くなり、広ければ変更の影響が広くなり、かつ特定も難しくなる。広すぎると意図しなかった箇所でのバグを招いてしまうこともある。よって結合度は疎である方が好ましい。

指標

結合度とはどのように測るのか?なにを持ってして結合度が低いと言えるのか?ちゃんと指標がある。サイズ、可視性、柔軟性だ。

  • サイズ

    • コンポーネント間の関係性の数
    • 関係性が多い例
      • 関数/メソッドの引数が多い
      • 共通のコードを利用する関数/メソッドが多くある
  • 可視性/Visibility

    • 第3者の理解のしやすさ
  • 柔軟性/Flexibilty

    • 要素を交換しても使えること
    • 柔軟性が高い例
      • 同じInterfaceを持つ別のクラスに交換しても使える
    • 柔軟性が低い例
      • 引数にPrimitiveな型を使わず抽象的なオブジェクトを使っている

分類

結合度について議論するとき上述の3つの指標を一々、当てはめることはない。結合度の程度の分類が存在する。結合度について議論する際はこの分類を利用することが多い。

結合度が高いもの(Worst)から低いもの(Best)を以下に説明する。

なお説明が長くなってしまったので強引に短くするとこんな感じだろうか。

内部動作依存 > グローバル変数 > 通信機能共有 > 処理フラグ渡し > 全データ丸投げ > 引数渡し > 引数なし > 通信なし
  • 内容結合(Content coupling)
    • あるモジュールが別のモジュールの内部動作によって変化したり依存したりする
    • あるモジュールの内部動作の変更が他モジュールに波及してしまう
    • 例:別のモジュールの内部データを直接参照する
  • 共通結合(Common coupling)
    • グローバル結合とも呼ばれる
    • 二つのモジュールが同じグローバル変数を共有する
    • 共通のリソースを変更すると両方のモジュールに波及する
  • 外部結合(External coupling)
  • 制御結合(Control coupling)
    • あるモジュールに何をすべきかについての情報(処理フラグなど)を渡すことで、別のモジュール処理の流れを制御する。
  • スタンプ結合(Stamp coupling)
    • 複数のモジュールが複合データ構造を共有し、その一部のみを使用する
    • モジュールが必要としないデータが変更されたときそのモジュールにも波及する
    • 例:全レコードの中の1つのフィールドを必要とする関数に全レコードのデータの構造体を渡す
  • データ結合(Data coupling)
    • モジュールを介してデータを共有する場合
    • 例:引数
  • メッセージ結合(Message coupling)
    • 最も結合度が低い結合の種類である
    • 引数のないメソッドの呼び出し
    • メッセージパッシング
  • 無結合(No coupling)
    • モジュールが相互に全く通信を行わない。