ソフトウェアアーキテクチャの基礎
1. イントロダクション
ソフトウェアアーキテクチャとは
- 明確な定義はないが「重要なもの」
- 非常に広範囲である
- 変化と拡大を続けている
- 文脈なしに成り立つ銀のアーキテクチャは存在しない
ソフトウェアアーキテクチャを構成する 4 要素
- 構造
- 単にスタイルのこと(マイクロサービス/レイヤード...)
- これだけではアーキテクチャ全体は定まらない
- アーキテクチャ特性
- システムに直接的には関わらないものの、成功のために必要な基準のこと
...ility
(可用性、信頼性、スケーラビリティ、セキュリティ...)
- アーキテクチャ決定
- システムをどう構築するかのルール
- 許されることと許されないことを決めたもの
- 設計指針
- ルールではなく、こうしたほうがいいよ的なガイドライン
アーキテクトがやるべきこと
- アーキテクチャ決定を下したり、設計指針を定義したりする
- アーキテクチャを継続的に分析・改善する
- 最新のトレンドを把握する
- 決定の遵守を徹底する
- 広く浅く多様なものに触れ、経験し、知る
- 事業ドメインの知識を学ぶ
- 対人スキルを身につける
- 政治を理解し、舵取りをする
プロセスとエンジニアリングプラクティスを分けて考えろ、後者に注目せよ
- プロセス
- チームの作り方、ミーティングの進め方、ワークフローの整理方法などを決めるもの
- e.g. スクラム、ウォーターフォール...
- 未知の未知には対処しやすいのはイテレーティブなプロセス
- エンジニアリングプラクティス
- 再現性のある、プロセスに依存しない、技術的な方法論
- e.g. 継続的インテグレーション、テスト駆動開発、自動化...
開発から運用までトータルで見た方が良い結果を生むよね 、というのが DevOps という考え方。
- アーキテクチャの法則
- すべてはトレードオフである
- How よりも Why を重視せよ
2. アーキテクチャ思考
アーキテクチャ思考とは、アーキテクトの目線で物事を見ること。
アーキテクチャはアーキテクトが作り、設計は開発者が作るもの。 ただし、その流れは一方向ではなく、双方向に影響し合うものでなければならない。
アーキテクトは、技術の深さよりも幅が大事。 これを認識しておかないと、全ての技術を習得しようとしてボロボロになったり、古く陳腐化した専門性で戦ったりしてしまう。
アーキテクチャとは Google で見つけられないものである。 つまり、"It depends"であり、すべてはトレードオフなのだ。 プラス面とマイナス面を洗い出し、どれを大事なのか考えよう。
ビジネスドライバーの求める要件を、アーキテクチャ特性(ility)に変換せよ。 このためには事業ドメインの知識と、ステークホルダーとの協力的な関係が必要。
アーキテクチャのスキルだけではなく、コーディングスキルを維持することも大事。うまくやるには以下がおすすめ。
- ボトルネックにならない部分の開発をする。例えば、負債解消、バグ修正、小さな機能追加など。
- PoC を頻繁に行う。この時、なるべくプロダクションレベルのコードにすること。ひどいコードが再 利用されることを防ぐためと、自身の鍛錬のため。
3. モジュール性
定義
モジュールとは、関連するコードを論理的にグループ化したもの。モジュール性とは、グループ化がどれだけうまく行われているかを示す指標。
ほとんどの言語にはモジュールを定義するための仕組み、例えばクラスや関数や名前空間などが備わっている。開発者はそれらを活用しながらモジュール性を実現していく。
ソフトウェアは常にエントロピー増大の危機にさらされており、良い状態を保つには常にエネルギーを費やし続けて秩序(≒ モジュール性)を維持する必要がある。
モジュール性の話はアーキテクチャ特性には一般的には含まれず暗黙的ではあるものの、重要な要素である。
モジュール性の指標とその計測手法
凝集度 / Cohesion
モジュール内の要素がどれだけ密接に関連しているかを示す指標で、高いほど良い。凝集度が低いということは、別のモジュールと結合しないかぎり有益な結果がえられないということだから。
LCOM という、フィールドを介して結合されていないメソッド群の数により、クラスの凝集度の低さを計測する方法がある。
結合度
モジュール間の依存関係の強さを表す指標。
求心性結合(Afferent Coupling)は外部からの接続数であり、これが多いということは、他の複数のモジュールからよく利用されている状態を指す。システムにとって重要なサービスを提供している可能性がある。
遠心性結合(Efferent Coupling)は外部に向いた接続数であり、これが多いということは、他の複数のモジュールを利用している状態を指す。他のモジュールに多く依存しているため、変更の影響を受けやすい可能性がある。
不安定度
求心性・遠心性結合のバランスを示す指標。両方の結合に占める遠心の割合が高いほど、不安定度が高いといえる。
抽象度
モジュール内における、抽象(抽象クラスやインターフェース等)と実装の比率のこと。抽象度が高いということは、モジュールが抽象的で、実装の詳細が隠蔽されているということ。
main メソッドしかない巨大な関数なら抽象度は 0 で、逆にインターフェースだけのクラスなら抽象度は 1 になる。
主系列からの距離
主系列からの距離は、ソフトウェアのモジュール性において、抽象度と不安定度という 2 つの指標のバランスを評価するための概念である。主系列からの距離が遠いということは、以下の 2 つの可能性を示唆する。
- 無駄ゾーン: モジュールの利用者が少ないにもかかわらず、抽象化が進んだ状態。全部ベタで書けばすむんじゃないの?的な。
- 苦痛ゾーン: モジュールの利用者が多いにもかかわらず、抽象化がされていない状態。変更と保守を容易にするために抽象化しようぜ!的な。
コナーセンス / Connascence
コナーセント = 接続
コナーセンスは、ソフトウェアの変更容易性を測るための概念である。2 つのコンポーネント間にコナーセンスがある場合、一方を変更すると他方にも変更が必 要になる。コナーセンスの種類とその解説は以下の通り。1-5 は静的なコナーセンス、6-9 は動的なコナーセンスである。1 に近いほど、より良く、より弱く、より変更容易性が高い。
-
名前のコナーセンス (Connascence of Name - CoN)
- 変数名、関数名、メソッド名、クラス名、モジュール名等、名前による参照のこと
- 例: あるメソッド名が変更されると、そのメソッドを呼び出している全ての箇所も修正が必要になる
-
型のコナーセンス (Connascence of Type - CoT)
- 型による参照のこと
- 例: ある型が変更されると、その型を使っている全ての箇所も修正が必要になる
-
意味のコナーセンス (Connascence of Meaning - CoM)
- あるコンポーネントが、他のコンポーネントの動作や意味に依存している状態です。Magic Number など。
- 例: 真を
1
と仮定して書かれているコード群は、その変数の意味が変更されると全て修正が必要になる。なお、Magic Number 等の代わりに名前付き定数を使うことで「名前のコナーセンス」に昇格できる。
-
位置のコナーセンス (Connascence of Position - CoP)
- あるコンポーネントが、他のコンポーネントの物理的な位置に依存している状態です。
- 例: 引数の順序
-
アルゴリズムのコナーセンス (Connascence of Algorithm - CoA)
- 複数のコンポーネントが、同じアルゴリズムに依存している状態です。
- 例: ある暗号化アルゴリズムを使用しているコードは、そのアルゴリズムが変更されると修正が必要になります。
-
実行順序のコナーセンス (Connascence of Execution - CoE)
- あるコンポーネントの実行が、他のコンポーネントの実行順序に依存している状態です。
- 例: ある関数が他の関数の後に実行されることを前提としているコードは、実行順序が変更されると修正が必要になります。
-
タイミングのコナーセンス (Connascence of Timing - CoT)
- あるコンポーネントの実行が、他のコンポーネントの実行タイミングに依存している状態です。
- 例: 複数スレッド間で同時に実行されると競合が発生するため、同期処理が必要になるケースなど
-
値のコナーセンス (Connascence of Value - CoV)
- 複数のコンポーネントが、同じ値に依存している状態です。
- 例: 分散トランザクションのように、全ての値を同時に更新するか、全く更新しないかのどちらかでなければならない場合など
-
アイデンティティのコナーセンス (Connascence of Identity - CoI)
- 複数のコンポーネントが、同じオブジェクトのインスタンスに依存している状態です。
- 例: あるシングルトンオブジェクトにアクセスするコードは、そのシングルトンオブジェクトが変更されると修正が必要になります