『Clean Architecture』再読(二)
プログラミングパラダイムとはプログラムの書き方の型であり、どのようなときにどのコード構造を採用すべきかを示すものです。
これまでに登場したプログラミングパラダイムは、構造化プログラミング、オブジェクト指向プログラミング、関数型プログラミングの三つです。
著者によると、それぞれのパラダイムはアーキテクトの武器を増やすものではありません。アーキテクトやプログラマーはすでに十分な武器を持っており、この三つのパラダイムは、その武器の使い方を制限するものです。だからこそ「パラダイム」と呼ばれます。
構造化プログラミング
構造化プログラミングは、プログラムの制御の直接的な移動を制限し、規律を与えます。特に、プログラム内でgotoを自由に使うことを制限します。
プログラムの分解
Dijkstraは数学的な導出によってプログラムを推論し、証明することを目指しました。プログラムをユークリッド的な構造にすることで、証明済みの構造をつなぎ合わせて新しいプログラムを作り、プログラム全体の正しさを導けると考えたのです。
また、プログラムにgotoが多いと分解が難しくなる一方、goto文の役割は分岐と反復で十分に果たせることにも気づきました。
このように構造化プログラミングは、プログラムを細かく分解できることを示しています。大きな問題を一連の高レベル関数の組み合わせに分解し、それぞれをさらに低レベルの関数へ分解することを、再帰的に続けられます。さらに、分解して得られた各関数も構造化プログラミングで記述できます。
しかし、このような形式的証明に基づくプログラミングは主流にはなりませんでした。現在は、科学的方法による検証がより広く使われています。
科学的方法による検証
科学理論や科学法則は反証できますが、証明することはできません。同様に、プログラムもテストによって反証できるだけで、正しさを証明することはできません。つまり「テストはバグの存在を示せるだけで、バグがないことは証明できない」のです。科学的方法を使うと、構造化プログラミングは、まずプログラムを証明可能な小さな関数へ再帰的に分解し、それから関連するテストを書いて、それらの関数が誤っていることを示そうとするよう促します。テストで反証できなければ、関数は十分に正しいとみなし、プログラム全体も正しいと推論できます。
オブジェクト指向プログラミング
オブジェクト指向プログラミングは、プログラムの制御の間接的な移動を制限し、規律を与えます。
具体的には、オブジェクト指向プログラミングはポリモーフィズムによって関数ポインタの利用を制限します。関数ポインタが指せる対象に制限を設けることだと考えられます。たとえばjavaのポリモーフィズムは、一般に継承関係にある二つのクラスのオブジェクト間で使われます。
著者はポリモーフィズムをOOPの最大の特徴と考えています。ポリモーフィズムを使えば、依存性の逆転を実現できます:
依存性の逆転とは、インターフェースを導入することで、システムの制御の流れに縛られず、すべてのソースコードの依存関係を完全に制御することです。
依存性の逆転によって、システムをプラグイン方式で開発できます。たとえばweb UIやデータベースと業務ロジックの依存関係を逆転させれば、中核となる業務ロジックをUIやデータベースから切り離せます。こうして、UIやデータベースは業務ロジックのプラグインになります。
オブジェクト指向プログラミングとは、ポリモーフィズムを使ってソースコードの依存関係を制御する能力です。この能力により、アーキテクトは高レベルの方針を担うコンポーネントと低レベルの実装を担うコンポーネントを分離したプラグインアーキテクチャを構築できます。低レベルのコンポーネントはプラグインとしてコンパイルでき、高レベルのコンポーネントとは独立して開発、デプロイできます。
関数型プログラミング
関数型プログラミングは、プログラム内の代入を制限し、規律を与えます。
言い換えると、関数型プログラミング言語の変数は不変です。
著者は次のように述べています。以下は原文からの引用です:
「すべての競合状態、デッドロック、並行更新の問題は、可変変数によって生じる。変数が一切変更されなければ、競合状態や並行更新の問題は起こり得ない。ロックの状態が不変であれば、デッドロックの問題も起こり得ない。」
記憶装置やプロセッサの速度の制約を考えなければ、不変性は実現できます。しかし実際にはこれらを考慮する必要があるため、不変性を適用できる範囲には限界があります。不変性を現実的に活用するためには、可変性の分離が必要です。
可変性の分離
可変性を分離する一般的な方法の一つは、アプリケーションやその内部サービスを分割して、可変コンポーネントと不変コンポーネントに分けることです。不変コンポーネントは純粋関数を使って処理を行い、その間に状態を変更しません。変数の状態を変更する必要があるときは、一つ以上の非関数型コンポーネント、つまり可変コンポーネントと通信します。
例としてGITバージョン管理ツールがあります。ポインタを移動することでファイルの追加、削除、変更を記録しますが、実際にファイルが変更されたり削除されたりするわけではありません。あるのは追加と検索だけです。これも不変性の一例ではないでしょうか。
mysqlのトランザクション管理やトランザクショナルメモリの動作にも、この考え方が使われています。
まとめ
三つのプログラミングパラダイムは、ソフトウェアアーキテクチャと密接に関係しています:
- ポリモーフィズムは、境界を越えるための手段です。
- 関数型プログラミングは、データの保存場所とアクセス権を規定し、制限するための手段です。
- 構造化プログラミングは、各モジュールの実装の基礎です。
三つのパラダイムは、ソフトウェアアーキテクチャの三つの主要な関心事と一致します。すなわち、コンポーネントの独立性(オブジェクト指向)、データ管理(関数型)、そして機能(構造化)です。
改めて考える
三つのプログラミングパラダイムはいずれも、プログラマーに新たな制限を課します。それぞれが特定のコードの書き方を制約しており、新しい能力を追加するものではありません。つまり、パラダイムが教えてくれるのは「何をしてはいけないか」です。
(第二回・完)