メインコンテンツまでスキップ

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

3. モジュール性

定義

モジュールとは、関連するコードを論理的にグループ化したもの。モジュール性とは、グループ化がどれだけうまく行われているかを示す指標。

ほとんどの言語にはモジュールを定義するための仕組み、例えばクラスや関数や名前空間などが備わっている。開発者はそれらを活用しながらモジュール性を実現していく。

ソフトウェアは常にエントロピー増大の危機にさらされており、良い状態を保つには常にエネルギーを費やし続けて秩序(≒ モジュール性)を維持する必要がある。

モジュール性の話はアーキテクチャ特性には一般的には含まれず暗黙的ではあるものの、重要な要素である。

モジュール性の指標とその計測手法

凝集度 / Cohesion

モジュール内の要素がどれだけ密接に関連しているかを示す指標で、高いほど良い。凝集度が低いということは、別のモジュールと結合しないかぎり有益な結果がえられないということだから。

LCOM という、フィールドを介して結合されていないメソッド群の数により、クラスの凝集度の低さを計測する方法がある。

結合度

モジュール間の依存関係の強さを表す指標。

求心性結合(Afferent Coupling)は外部からの接続数であり、これが多いということは、他の複数のモジュールからよく利用されている状態を指す。システムにとって重要なサービスを提供している可能性がある。

遠心性結合(Efferent Coupling)は外部に向いた接続数であり、これが多いということは、他の複数のモジュールを利用している状態を指す。他のモジュールに多く依存しているため、変更の影響を受けやすい可能性がある。

不安定度

求心性・遠心性結合のバランスを示す指標。両方の結合に占める遠心の割合が高いほど、不安定度が高いといえる。

抽象度

モジュール内における、抽象(抽象クラスやインターフェース等)と実装の比率のこと。抽象度が高いということは、モジュールが抽象的で、実装の詳細が隠蔽されているということ。

main メソッドしかない巨大な関数なら抽象度は 0 で、逆にインターフェースだけのクラスなら抽象度は 1 になる。

主系列からの距離

主系列からの距離は、ソフトウェアのモジュール性において、抽象度と不安定度という 2 つの指標のバランスを評価するための概念である。主系列からの距離が遠いということは、以下の 2 つの可能性を示唆する。

  • 無駄ゾーン: モジュールの利用者が少ないにもかかわらず、抽象化が進んだ状態。全部ベタで書けばすむんじゃないの?的な。
  • 苦痛ゾーン: モジュールの利用者が多いにもかかわらず、抽象化がされていない状態。変更と保守を容易にするために抽象化しようぜ!的な。

コナーセンス / Connascence

コナーセント = 接続

コナーセンスは、ソフトウェアの変更容易性を測るための概念である。2 つのコンポーネント間にコナーセンスがある場合、一方を変更すると他方にも変更が必要になる。コナーセンスの種類とその解説は以下の通り。1-5 は静的なコナーセンス、6-9 は動的なコナーセンスである。1 に近いほど、より良く、より弱く、より変更容易性が高い。

  1. 名前のコナーセンス (Connascence of Name - CoN)

    • 変数名、関数名、メソッド名、クラス名、モジュール名等、名前による参照のこと
    • 例: あるメソッド名が変更されると、そのメソッドを呼び出している全ての箇所も修正が必要になる
  2. 型のコナーセンス (Connascence of Type - CoT)

    • 型による参照のこと
    • 例: ある型が変更されると、その型を使っている全ての箇所も修正が必要になる
  3. 意味のコナーセンス (Connascence of Meaning - CoM)

    • あるコンポーネントが、他のコンポーネントの動作や意味に依存している状態です。Magic Number など。
    • 例: 真を1と仮定して書かれているコード群は、その変数の意味が変更されると全て修正が必要になる。なお、Matci Number 等の代わりに名前付き定数を使うことで「名前のコナーセンス」に昇格できる。
  4. 位置のコナーセンス (Connascence of Position - CoP)

    • あるコンポーネントが、他のコンポーネントの物理的な位置に依存している状態です。
    • 例: 引数の順序
  5. アルゴリズムのコナーセンス (Connascence of Algorithm - CoA)

    • 複数のコンポーネントが、同じアルゴリズムに依存している状態です。
    • 例: ある暗号化アルゴリズムを使用しているコードは、そのアルゴリズムが変更されると修正が必要になります。
  6. 実行順序のコナーセンス (Connascence of Execution - CoE)

    • あるコンポーネントの実行が、他のコンポーネントの実行順序に依存している状態です。
    • 例: ある関数が他の関数の後に実行されることを前提としているコードは、実行順序が変更されると修正が必要になります。
  7. タイミングのコナーセンス (Connascence of Timing - CoT)

    • あるコンポーネントの実行が、他のコンポーネントの実行タイミングに依存している状態です。
    • 例: 複数スレッド間で同時に実行されると競合が発生するため、同期処理が必要になるケースなど
  8. 値のコナーセンス (Connascence of Value - CoV)

    • 複数のコンポーネントが、同じ値に依存している状態です。
    • 例: 分散トランザクションのように、全ての値を同時に更新するか、全く更新しないかのどちらかでなければならない場合など
  9. アイデンティティのコナーセンス (Connascence of Identity - CoI)

    • 複数のコンポーネントが、同じオブジェクトのインスタンスに依存している状態です。
    • 例: あるシングルトンオブジェクトにアクセスするコードは、そのシングルトンオブジェクトが変更されると修正が必要になります

コナーセンスのベストプラクティス

  • 強いコナーセンスは弱いコナーセンスに置き換える
  • コード間の距離が遠くなるにつれ、弱いコナーセンスを使用する
  • システムを分割して全体的なコナーセンスを最小に抑える

9. アーキテクチャスタイル

アーキテクチャパターンともいう。

基礎的なパターン

例えば「レイヤー」の概念は古くから存在する。

巨大な泥団子 / Big Ball of Mud はアーキテクチャ構造が存在しないクソコードのこと。残念ながら現実世界で非常によく見られる。

1 層アーキテクチャ(ユニタリーアーキテクチャ)は、ただ 1 つのコンピューターとその上で動くソフトウェアのこと。コンピューター黎明期のメインフレームなどが該当する。現代にはあまり存在しない。

2 層アーキテクチャ(クライアント・サーバー)は、Access のようなデスクトップアプリ + DB サーバーの構成や、現在の Web アプリのようなブラウザ + Web サーバーの構成を指す。

3 層アーキテクチャもある。JS が動くフロントエンド + Java などのアプリケーションサーバー + 強力な商用 DB サーバーなどの構成からなる。90 年代後半に流行った。

モノリシック vs 分散

  • モノリシック
    • レイヤードアーキテクチャ
    • パイプラインアーキテクチャ
    • マイクロカーネルアーキテクチャ
  • 分散
    • サービスベースアーキテクチャ
    • イベント駆動アーキテクチャ
    • スペースベースアーキテクチャ
    • サービス指向アーキテクチャ
    • マイクロサービスアーキテクチャ

分散アーキテクチャは強力だが、以下のような問題に対処する必要があり、大きなトレードオフが発生する。


ネットワークは信頼できないので、タイムアウトやサーキットブレーカーのような機構が必要になる。

関数呼び出しのレイテンシーが大きい。ローカル呼び出しならナノ秒で住むが、RPC などはミリ秒単位になる。ロングテールレイテンシー(95-99%タイルのレイテンシー)を把握して必要な対処をすることが大事。

帯域幅は有限であるため、例えば GraphQL のような必要最低限のデータを取得する仕組みがないと、あっという間に食いつぶす。

ネットワークは安全ではない。分散システムは攻撃を受ける表面積が飛躍的に増えるので、高度なセキュリティ対策が必要になる。

常に変化するネットワークトポロジー(接続形態)への対応が必要。些細な機器の更新がレイテンシーに影響を与えた結果、タイムアウトを引き起こしてシステムダウンするようなこともある。

ネットワーク管理者はたくさんいる。コミュニケーションを取るべき管理者は一人では済まず、コストがかかる。

転送コストがかかる。これはレイテンシーの話ではなく RESTful 呼び出しにかかる費用の話で、モノリスと比べるとかなり高い。

ネットワークにはムラがある。ネットワーク内には違なるメーカーの様々な機器が存在するし、それらが協調して完全にうまく動く保証はない。

ロギングが難しい。分散ロギングしないと問題の追跡すらできない。

トランザクションが難しい。分散トランザクションにより結果整合性を担保するのが関の山。ACID トランザクションのように即時の一貫性は保証できない。