基礎: コンポーネントベース設計
アプリケーションプログラミングインターフェイス(API)には、以下の要素のうち1つ以上が含まれるとします。
- データ型定義(構造体、列挙型など)
- 関数宣言
- 定数
- リモートのリクエスト-レスポンス形式の仕様。たとえば:
John Ousterhout3 によると、「深いモジュール」とは、適切に設計された API が、その背後にある氷山のような複雑さを隠蔽、つまり抽象化しているモジュールのことです。
深いモジュールには、多くの場合、コードベースの保守やリファクタリングを容易にするという利点があります。 優れた抽象化は、API を最初に学ぶことや正しく使うことも容易にします。 そのインターフェイスが外部の顧客向けであるか、コードベース内の別のコンポーネント向けであるかを問いません。
複数のコンポーネントを持つ大規模システムの累積的な複雑さを考えると、深さは特に重要になります。
ほとんどの文脈では、モジュールとコンポーネントは同義です。 しかし本書では、コンポーネントは1つ以上のモジュールで構成されるものとします。 つまり、コンポーネントはより大きな(複数モジュールから成り得る)部品です。 この違いをより明確にする図を、まもなく見ていきます。
「深いモジュール」の実例は何でしょうか?
Ousterhout は典型例として、ユーザー空間アプリケーションがハードウェア関連および/または特権サービスを要求するための OS の仕組みであるシステムコール3を挙げています。
少数の呼び出しが、たとえば特定の種類で特定のベンダーが製造した物理ハードディスクにファイルを書き込む、といった生々しい詳細を抽象化します。 OS は外部に対して小さく安定した API を提供し、その一方で内部では固有の複雑さを自由に管理できます。
本書の中核となるプロジェクトも、他の多くの動的コレクションと同様に、深いモジュールであると主張できます。 標準ライブラリのコレクションに対する API 互換の代替を提供しつつ、基盤となるデータ構造やメモリ管理戦略の具体的な内容は抽象化しています。
したがって、Rust のモジュールシステムであれ、他の言語における同等の仕組みであれ、何らかのコード編成機能を活用する際の目標は、「深い」モジュールを作成し、それらを「疎」結合のコンポーネントへまとめることです。 つまり、小さな API サーフェスを通じて豊富な機能を提供する、十分に分離された部品を意味します。
一般に、このアプローチにより、新しいチームメンバーにとって扱いやすく、全員にとって改善しやすいコードベースになります。 つまり、火消しに費やす時間が減り、新機能を出荷する時間が増えるということです。 そして、複雑さを抑制することで、セキュリティと信頼性のリスクも低減できます。
効果的なコンポーネント化
深さは自然と、結合度を最小化し、凝集度を最大化する傾向があります。定義は次のとおりです4。
-
結合度: API 間の相互依存性の尺度。
- 例: 公開シグネチャで同じカスタムデータ型に相互依存していること。または、非公開のグローバル共有状態。
-
凝集度: API の個々の要素間にある共通性の尺度。
- 例: モジュールが公開する関数には、互いに明確な論理的関係があるか? そうであれば、そのモジュールは高い凝集度を持ちます。
低結合・高凝集のコンポーネントは一般に望ましいものですが、常に実用的であるとは限りません。 たとえば、機能を集中化した部品は、より高速なアルゴリズムやよりセキュアな実装へ置き換えやすくなります。 しかし、集中化は結合度を高めることもあります。
同様に、過度に具体的な API は将来に備えにくく、新しい要件が破壊的変更を意味する場合があります。 しかし、API が汎用的すぎると、凝集度を下げることなく現在の具体的なニーズを満たすために、煩雑なラッパーが必要になる可能性が高くなります。
コンポーネントベース設計の可視化
モジュールの公開 API が少なく単純なコンポーネントは、多くの場合、安定性の負担が少なく、誤用される可能性も低くなります。 そのようなコンポーネントは、大規模で野心的な複数コンポーネントシステムをより効果的に構成する助けになります。
視覚的には、これは、コンポーネントが互いの内部を公開し、それに依存する脆弱なシステムから離れることを意味します。
そして、(同じ機能を提供しながら)内部の複雑さを抽象化する俊敏なシステムへ向かうことを意味します。
ここで「俊敏」とは、オンボーディング、拡張、リファクタリングが容易なコードベースを意味します。 ソフトウェア開発フレームワーク群の総称である Agile5 ではありません。
どちらの設計でも、各コンポーネント内のモジュール数(6)は変わっていないことに注意してください。 機能を削除しているのではなく、外部向けの複雑さだけを取り除いているのです。 エンドユーザーの認知負荷は軽減されます。 総「作業量」は変わりません。
計画と反復の重要性
複雑さは、生産性とセキュリティの両方の敵です。 しかし、本番環境に到達する機能の最初のイテレーションが、洗練されて作り込まれたものになるとは限りません。 ほとんどの商業的文脈では、完璧を目指すことは現実的ではありません。
その代わりに、最初のバージョンを適切に設計されたものにすることを目指せます。 それは、組織やチームの現在の品質基準を水準として使うことを意味するかもしれません。 そして、期限内に提供しつつ、その水準を少し高く押し上げるよう努めることです。
初期アーキテクチャが、そのシステムのライフサイクル全体を通じて固定されてしまうものになることもあります。 したがって、前もって設計時間を確保することは、大きな見返りを生む可能性があります。 本番インフラストラクチャでは、その結果として、障害や侵害に関する午前3時の電話の件数を減らせるかもしれません。 しかし、平均的なケースである計画保守も、適切に設計されたシステムでは低コストになります。
高価値なシステムにとって最良の設計は、ほとんど常に反復の結果です。 既存システムを大幅にリファクタリングする機会、または後継システムをゼロから作成する機会がある場合、得られた教訓を適用できます。 したがって、今日の時点で抜本的な変更を正当化できないとしても、現在の制約を明日のために記録しておく価値はあります。
まとめ
低複雑度のシステムは、より信頼性が高く、保守しやすく、セキュアである傾向があります。 複雑さを抑制するには、通常、低結合・高凝集を目指して設計することを意味します。 深いモジュールは、この両方の目標に適しています。
-
REST API とは?. RedHat(2020)。 ↩
-
コア概念、アーキテクチャ、ライフサイクル. Google(2022年アクセス)。 ↩
-
[個人的なお気に入り] ソフトウェア設計の哲学. John Ousterhout(2021)。 ↩ ↩2
-
[個人的なお気に入り] Effective C: プロフェッショナル C プログラミング入門. Robert Seacord(2020)。 ↩