『Clean Architecture』再読(三)SOLID原則
ソフトウェアモジュールを構築する主な目標は三つあります:
- 変更に耐えられるソフトウェアにすること
- ソフトウェアを理解しやすくすること
- 複数のソフトウェアシステムで再利用できるコンポーネントを構築すること
SOLID原則は、データと関数をどのようにクラスへまとめ、それらのクラスをどのようにつなげてプログラムにするかを教えてくれます。SOLID原則はモジュール設計の指針であり、アーキテクチャ設計のレベルでは別の設計原則も使います。
SOLID原則とは、単一責任の原則SRP、オープン・クローズドの原則OCP、リスコフの置換原則LSP、インターフェース分離の原則ISP、依存性逆転の原則DIPを指します。
単一責任の原則 SRP
SRPは、各ソフトウェアモジュールが一つの機能に責任を持ち、モジュールを変更する理由は一つだけであるべきだと定めます。どのソフトウェアモジュールも、ある一種類のアクターだけに責任を持つべきです。
コンポーネントレベルのSRPは、共通閉鎖の原則CCPと呼ばれます。
モジュールの設計でSRPに従わないと、何が起きるのでしょうか。例を見てみましょう。
経理部門、人事部門、開発部門が、同じ給与・勤務時間計算プログラムに依存しているとします。プログラムには三つの関数があります:
- CalculateSalary:給与を計算する。
- CalculateTime:勤務時間を計算する。
- Save:情報を保存する。
三つの部門は問題なくプログラムを使っていました。ところがある日、経理部門の給与計算のルールが変わり、保守担当者がCalculateSalary関数を変更しました。すると奇妙なことに、人事部門や開発部門の給与も同時に変わってしまったのです……。
オープン・クローズドの原則 OCP
「追加を受け入れ、変更を避けよう!」
OCPによると、ソフトウェアシステムを変更しやすくするには、既存のコードの変更に頼るだけではなく、新しいコードの追加によって振る舞いを変えられる設計にしなければなりません。適切に設計されたソフトウェアは、拡張しやすく、既存部分の変更を必要としにくいものです。
OCPはクラスやモジュールの設計だけでなく、コンポーネントの設計にも適用できます。その起源はデバイス独立性の概念にあります。具体的には、依存性の逆転を使ってIOデバイスをプラグインとして設計すれば、既存のIOデバイスを変更せずに、新しいIOデバイスを簡単に追加できます。
OCPを実現するには、システムを一連のコンポーネントに分け、それらの依存関係を階層的に構成します。こうすることで、低レベルのコンポーネントが変更されても、高レベルのコンポーネントは影響を受けません。
リスコフの置換原則 LSP
LSPによると、置き換え可能なコンポーネントでソフトウェアシステムを構築したいなら、それらのコンポーネントは共通の契約に従い、互いに置き換えられなければなりません。
LSPの本質は置換可能性の原則です。型Sの各オブジェクトo1に対し、型Tのオブジェクトo2が存在し、T型を操作するプログラムPでo2をo1に置き換えても振る舞いが変わらないなら、SをTのサブタイプと呼べます。
LSPはソフトウェアアーキテクチャのレベルにも適用できますし、適用すべきです。置換可能性が破られると、それに対処するための複雑な仕組みをアーキテクチャに大量に追加しなければならなくなるからです。
インターフェース分離の原則 ISP
ISPによると、設計では不要な依存を避けるべきです。どのレベルのソフトウェア設計でも、必要のないものに依存すると、予期しない問題につながります。
依存性逆転の原則 DIP
DIPによると、高レベルの方針を表すコードは、低レベルの詳細を実装するコードに依存してはいけません。逆に、低レベルの詳細を実装するコードが、高レベルの方針を表すコードに依存すべきです。
依存性の逆転を直感的に理解するには、制御の流れとソースコードの依存の流れを考えます。たとえば制御がコンポーネントAからBへ流れる場合、BはA側の呼び出しに応じた実装を提供し、AはBのモジュールを取り込む必要があるため、コードの依存もAからBへ向かいます。この状態では、システムの振る舞いが制御の流れを決め、制御の流れがソースコードの依存関係を決めるため、アーキテクチャには選択の余地がありません。しかしインターフェースを導入すると、Bはそのインターフェースを実装し、Aはインターフェースを取り込むだけでよくなり、Bのモジュールを直接取り込む必要がなくなります。こうして制御の流れとソースコードの依存の向きが逆転します。これが「依存性の逆転」です。
依存性の逆転は、ソフトウェアアーキテクチャの設計に自由をもたらします。柔軟なシステムを設計するには、ソースコードレベルの依存関係で、具象実装よりも抽象型、たとえばインターフェースや抽象クラスを多く参照すべきです。
DIPから、いくつかのコーディング指針を導けます:
- コードでは抽象インターフェースを多く利用し、変化しやすい具象実装クラスの利用をできる限り避ける。
- 具象実装クラスから派生クラスを作らない。
- 具象実装を持つ関数をオーバーライドしない。
- 特定の具象実装に関係する名前や、変化しやすいものの名前をコードに書き込まない。
(第三回・完)