オブジェクト指向
設計
アーキテクチャ
CleanArchitecture

書籍「Clean Architecture」が最高すぎたのでエッセンスをまとめてみた

本記事では、書籍「Clean Architecture 達人に学ぶソフトウェアの構造と設計」のポイントを抽出する。ただ、削った部分も多いので、ぜひ書籍を購入してほしい。

第1部 イントロダクション

ソフトウェアを「一度だけ」動かすのは、それほど難しいことではない。正しくするのは難しい

ソフトウェアを正しくすると、不思議なことが起こる。開発や保守に必要な人材はわずかで済む。変更は簡単で迅速になる。欠陥の数は少なく、ほとんど出てこなくなる。労力は最小に抑えられ、機能性と柔軟性は最大になる。

「あとでクリーンにすればいいよ。先に市場に出さなければ!」ソフトウェア開発者たちはそう言ってごまかす。だが、あとでクリーンにすることはない。短期的にも長期的にも、崩壊したコードを書くほうがクリーンなコードを書くよりも常に遅い。早く進む唯一の方法は、うまく進むことである

すべてのソフトウェアシステムは、2つの異なる価値「振る舞い」と「構造」を提供する。

「ソフトウェアシステムは、動作することが重要だろうか?簡単に変更できることが重要だろうか?」こう質問すると、大半の人は動作することが重要であると答える。だが、これは間違った態度である。

  • 1つ目の価値「振る舞い(機能)」は緊急だが、常に重要とは限らない
  • 2つ目の価値「構造(アーキテクチャ)」は重要だが、常に緊急とは限らない

ソフトウェア開発者以外には、アーキテクチャの重要性を適切に評価できない。したがって、ソフトウェア開発者には、機能の緊急性よりもアーキテクチャの重要性を強く主張する責任が求められる

この責任を果たすことは「闘争」に足を踏み入れることを意味する。優れたソフトウェア開発チームは、真正面から闘争に立ち向かう。

第2部 構成要素から始めよ:プログラミングパラダイム

以下の3つのプログラミングパラダイムは、プログラマから能力を奪っている。新しい能力を提供しているものはない。「何をすべきか」ではなく、「何をすべきでないか」を伝えている

  • 構造化プログラミング - 直接的な制御の移行(goto文)に規律を課す
  • オブジェクト指向プログラミング - 間接的な制御の移行(関数ポインタ)に規律を課す
  • 関数型プログラミング - 代入1に規律を課す

構造化プログラミングは、モジュールを再帰的に分割することを可能にする。そのままでは解決が難しい「大きな問題」を「小さな問題」に分割していき、順次・反復・分岐という単純な制御構造で解決する。

オブジェクト指向言語は安全で便利なポリモーフィズムを提供している。アーキテクトにとってオブジェクト指向とは、「ポリモーフィズムを使用することで、システムにあるすべてのソースコードの依存関係を『絶対的に制御』する能力」である。

関数型言語の変数は変化しない。並行処理アプリケーションの「競合状態、デッドロック状態、並行更新の問題」の原因は、すべて可変変数であり、可変変数がなければ発生しない。イベントソーシングは、状態ではなく履歴(トランザクション)を保存するという戦略である。現在の状態が必要になれば、過去の履歴を全部取得すれば導出可能だ。

第3部 設計の原則

SOLID原則の目的は、「変更に強く、理解しやすく、多くのシステムで再利用できる」ソフトウェア構造を作ることである。SOLID原則は、関数やデータ構造をどのようにクラスに組み込むのか、クラスの相互接続はどうするのかといったことを教えてくれる。

  • 単一責任の原則(SRP:Single Responsibility Principle)
    • モジュールを変更する理由は1つでなければならない
  • オープン・クローズドの原則(OCP:Open/closed principle)
    • モジュールは拡張に対して開き、修正に対して閉じていなければならない
  • リスコフの置換原則(LSP:Liskov substitution principle)
    • 派生型はその基本型と置換可能でなければならない
  • インタフェース分離の原則(ISP:Interface segregation principle)
    • クライアントが利用しないメソッドへの依存を強制してはならない
  • 依存関係逆転の原則(DIP:Dependency inversion principle)
    • 「抽象」は実装の詳細に依存してはならない、実装の詳細が「抽象」に依存すべきである

ソフトウェアシステムに手を入れるのは、ユーザやステークホルダーを満足させるためである。このソフトウェアシステムの変更を望む人たちを「アクター」と呼ぶ。このアクターこそが、単一責任原則が指す「変更する理由」である。つまり、「モジュールはたったひとつのアクターに対して責務を負うべき」といえる。

依存関係逆転の原則は、上位レベルの方針の実装コードは、下位レベルの詳細の実装コードに依存すべきではなく、逆に詳細側が方針に依存すべきであるとしている。この原則は、アーキテクチャ全体の依存関係を制御するうえで要となる。

第4部 コンポーネントの原則

コンポーネントとは、デプロイの単位のことである。システムの一部としてデプロイできる、最小限のまとまりを指す。Javaならjarファイル、Rubyならgemファイル、.NETならDLLなどがそれにあたる。

コンポーネントの凝集性

コンポーネントの凝集性を高めるには、「不要なものに依存しないこと」「同じタイミング、同じ理由で変更されるものはひとまとめにすること」が重要である。

  • 全再利用の原則(CRP:Common Reuse Principle)
    • コンポーネントのユーザに対して、実際には使わないものへの依存を強要してはいけない
  • 閉鎖性共通の原則(CCP:Common Closure Principle)
    • 同じ理由、同じタイミングで変更されるクラスをコンポーネントにまとめる
    • 変更の理由やタイミングが異なるクラスは、別のコンポーネントに分ける
  • 再利用・リリース等価の原則(REP:Reuse-Release Equivalency Principle)
    • 再利用の単位とリリースの単位は等価になる

コンポーネントには一貫するテーマや目的がある。コンポーネントを形成するクラスやモジュールは凝集性のあるグループでなければいけない。

開発時の利便性と再利用性のトレードオフを考慮し、この3つの原則のバランスをとる必要がある。そして、そのバランスはプロジェクトの時期によっても変わっていく。

初期段階なら、開発しやすさが重要なので、「再利用・リリース等価の原則」よりも「閉鎖性共通の原則」のほうが重視される。一方、コンポーネントが複数プロジェクトから利用されるようになると、他のプロジェクトでどのように使われるか考えるようになり、「再利用・リリース等価の原則」の重要度が上がっていく。

コンポーネントの結合

コンポーネントを疎結合にするには、「循環依存を作らないこと」「抽象度が高くなる方向に依存すること」が重要である。

  • 非循環依存関係の原則(ADP:Acyclic Dependencies Principle)
    • コンポーネントの依存グラフに循環依存があってはならない
  • 安定依存の原則(SDP:Stable Dependencies Principle)
    • 安定度の高い方向に依存する
  • 安定度・抽象度等価の原則(SAP:Stable Absstractions Principle)
    • コンポーネントの抽象度は、その安定度と同程度でなければいけない

コンポーネントの構造をトップダウンで設計するのは不可能である。コンポーネントの構造は、システムの論理設計に合わせて育てていく。そのため、循環依存が入り込まないように、依存構造を常に注視しておく必要がある。循環依存があると、コンポーネントを切り離すのが難しくなる。ユニットテストもビルドもリリースも難しくなる。

システムの中には、上位レベルのアーキテクチャや方針を示すソフトウェアなど、頻繁に変更すべきでないモノが含まれる。これらは安定していてほしいが、一方で、安定度の高さが拡張の妨げにならないようにもしたい。そのためには、安定度の高いコンポーネントはインタフェースと抽象クラスで構成し、抽象度も高くする。安定度が高くて拡張可能なコンポーネントは柔軟になり、アーキテクチャへの制約も少なくなる。

第5部 アーキテクチャ

アーキテクチャとは

アーキテクチャの最終的な目的は「システムのライフタイムコストを最小限に抑え、プログラマの生産性を最大にすること」である。優れたアーキテクチャがあれば、システムを容易に開発・デプロイ・運用・保守できる。そのための戦略は、できるだけ長い期間、できるだけ多く選択肢を残すことである。その残すべき選択肢とは「重要ではない詳細」だ。

ソフトウェアシステムは、「方針」と「詳細」に分割できる。方針は、ビジネスのすべてのルールや手順を含んでいる。方針には、システムの本当の価値がある。「方針」と「詳細」は慎重に区別して、「方針」が「詳細」に決して依存することがないように切り離す。

詳細には、IOデバイス・データベース・ウェブシステム・サーバ・フレームワーク・通信プロトコルなどが含まれる。詳細の決定を延期や留保できれば、実験できる数が増え、挑戦できることが増え、決定しなければいけない時点までに入手できる情報が増える。

ビジネスルール

ビジネスルールとは、ビジネスマネーを生み出したり節約したりするルールや手続きのことである。ビジネスルールは、ソフトウェアシステムが存在する理由であり、中心的な機能であり、家宝である。ビジネスルールはシステムのなかで、最も独立していて、最も再利用可能なコードでなければいけない。

エンティティ

自動化されているか手動実行されるか問わず、ビジネスマネーを生み出したり節約したりできるルールを「最重要ビジネスルール」と呼ぶ。たとえば銀行ローンにN%の利子をつける、といった類のものだ。これは、ビジネスにとって欠かせないルールであり、システム化されていなくても存在する。

最重要ビジネスルールには、いくつかのデータが必要になる。たとえば、ローンであれば、貸付金残高、金利、支払いスケジュールなどが必要となる。こうしたシステムが自動化されていなくても存在するデータのことを「最重要ビジネスデータ」と呼ぶ。

最重要ビジネスルールと最重要ビジネスデータは密接に結びついているため、オブジェクトの有力な候補になる。これをエンティティと呼ぶ。エンティティは、UI・データベース・フレームワークについて何も気にする必要はなく、ビジネスを表すものとして切り離す。

ユースケース

ユースケースとは、自動化されたシステムを使用する方法を記述したものである。ユーザから提供された入力、ユーザに戻す出力、出力を生成する処理ステップなどを規定する。

エンティティに含まれる最重要ビジネスルールとは違い、ユースケースは「アプリケーション固有」のビジネスルールを記述している。ユースケースには、エンティティの最重要ビジネスルールをいつ・どのように呼び出すかを規定したルールが含まれる。

ユースケースはオブジェクトである。ユースケースクラスは、シンプルなリクエストデータ構造を受け取り、出力としてシンプルなレスポンスデータ構造を戻す。これらのデータ構造は、フレームワークから提供されるHttpRequestやHttpResponseなどに依存してはいけない。

独立性

レイヤーの切り離し

ソフトウェアシステムはUI、ビジネスルール、データベースなどの水平レイヤーで分割できる。たとえば、UIを変更する理由は、ビジネスルールを変更する理由とは関係がない。ゆえに、ユースケースのUI部分とビジネスルールの部分を分離する。また、データベース・クエリ言語・スキーマなども、ビジネスルールやUIとは関係ない技術的詳細である。

ビジネスルールは、アプリケーションに密接に結びついているものもあれば、一般的なものもある。たとえば、入力フィールドの検証は、アプリケーションと密接に結びついている。一方、口座の利率計算や在庫の計算といったビジネスルールは、そのドメインに密接に結びついている。この2種類のルールは異なる頻度や理由で変更される。よって、独立して変更できるように分離すべきである。

ユースケースの切り離し

ユースケースは、システムの水平レイヤを薄く垂直にスライスしたものである。それぞれのユースケースは、UIの一部、アプリケーション特有のビジネスルールの一部、アプリケーションに依存しないビジネスルールの一部、データベース機能の一部を使用する。

変更する理由の違いで切り離しておくと、古いユースケースに影響を与えずに新しいユースケースを追加できる。

重複

ソフトウェア開発の世界では、「重複は悪」とされる。しかし、明らかに重複していたコードが、変更理由が異なるために個別に進化を遂げ、数年後には両者がまるで違ったものになっていることがある。これは「本物の重複」ではない。

例えば、画面構成が同じ2つのユースケースがあった場合、時間経過とともに少しずつ違った画面になり、最終的に別物になっている可能性が高い。

同様に、あるデータベースのレコードのデータ構造が、ある画面のデータ構造とよく似ている場合がある。データベースのレコードをそのままUIに渡したくなるが、これはほぼ確実に「偶然の重複」である。

反射的に重複を排除してはいけない。「本物の重複」かどうかを見極めるべきだ。

境界線を引く

早すぎる決定との結合は、人々のパワーを奪う。早すぎる決定とは、システムのビジネス要件(ユースケース)と関係のない決定である。たとえば、フレームワーク・データベース・ウェブサーバ・ユーティリティライブラリ・DIなどに関する決定が含まれる。

ソフトウェア開発者と顧客は、システムについて見誤ることが多い。GUIを見て、GUIがシステムだと思っている。重要な原則は「IOは無関係」である。UIはビジネスルールにとって重要ではない。

アーキテクチャとは、境界線を引く技芸である。ソフトウェアの要素を分離し、お互いのことがわからないように制限する。境界線は「重要なもの」と「重要ではないもの」の間に引く。境界線を挟んだコンポーネントは、それぞれ変更の頻度や理由が違っている

叫ぶアーキテクチャ

あなたのアプリケーションのアーキテクチャはなんと叫んでいるだろうか?最上位レベルのディレクトリ構造と最上位レベルのパッケージのソースファイルは、「ヘルスケアシステム」「会計システム」「在庫管理システム」と叫んでいるだろうか?それとも「Rails」「Spring/Hibernate」「ASP」と叫んでいるだろうか?

優れたアーキテクチャは、ユースケースを強調し、周辺の関心事からユースケースを切り離す。アーキテクチャがユースケースをサポートし、フレームワークから少し距離を置いたものになっていれば、フレームワークを使うことなく、すべてのユースケースのユニットテストを実行できる。

アーキテクチャは、使用しているフレームワークではなく、システムそのものについての情報を伝える必要がある。

Humble Object

Humble Objectパターンでは、テストしにくい振る舞いとテストしやすい振る舞いを、2つのモジュールに分割する。テストが難しい振る舞いが含まれるモジュールをHumble Objectと呼ぶ。

たとえば、GUIはテストが難しいので、ViewとPresenterに分離する。ViewはHumble Objectでテストが難しいのでシンプルに保つ。Presenterはテスト可能なオブジェクトで、たとえば日付などの文字列を適切にフォーマットして、Viewが参照できるViewModelというシンプルなデータ構造に配置する。

アプリケーションがデータベースや外部システムと通信する必要がある場合なども、Humble Objectパターンが境界を作る。振る舞いをテストしやすい部分とテストしにくい部分に分割することが、アーキテクチャの境界の定義につながる。アーキテクチャの境界でHumble Objectパターンを使用すると、システム全体のテスト容易性が大幅に向上する。

レイヤーと境界

境界を完全に構築しようとすると、初期コストが高くつくことを認識する必要がある。同時に、境界を無視すると、境界を追加するコストが高くなることも認識する必要がある。

YAGNIでは、抽象化が必要になることを予測してはいけないと提唱している。だが、アーキテクチャの境界が必要なところになかったとしたら、境界を追加するコストやリスクは非常に高いものになる。

頭を使って推測するべきだ。コストを評価し、アーキテクチャの境界がどこにあるのか、完全に実装する必要があるのか、部分的に実装すべきなのか、無視したほうがいいのかを判断しなければいけない。しかもこれは、一回限りの決定ではない。常に注意を払う必要がある

テスト境界

テストは、非常に詳細で具体的であり、テストするコードに対して常に依存している。テストはシステムの設計にうまく統合しないと、不安定なものになる。もちろん問題は結合である。共通のシステムコンポーネントを変更すると、何百何千とテストが壊れる可能性がある。脆弱なテストは、プロダクションコードを硬直化させる

プロダクションコードの構造と深く結びついたテストは、大量のテストを変更する必要がある。時間がたつにつれて、テストは具体的かつ個別化する傾向にあるが、プロダクションコードは抽象的かつ一般化する傾向がある。しかし、プロダクションコードとテストコードの結合が強いと、こうした進化が妨げられる。

テストもうまく設計すべきシステムの一部である。

第6部 詳細

データベースは詳細

データアクセスフレームワークの多くは、データベースの行やテーブルをオブジェクトとして受け渡しできるようになっている。これは、アーキテクチャ的には間違っている。ユースケース、ビジネスルール、そして場合によってはUIまでも、リレーショナルデータの構造に縛られてしまう

リレーショナルデータベースは、データを表形式で管理して、SQLからアクセスする仕組みである。データこそが重要で、データベースは詳細である。

ウェブは詳細

GUIは詳細である。ウェブはGUIである。したがって、ウェブは詳細である。

ウェブを入出力デバイスの一種と考えよう。UIとアプリケーションの間には境界がある。UIとアプリケーションの間のダンスのどこかに、入力データが完成してユースケースが実行可能になるところがある。

フレームワークは詳細

フレームワークの作者は、あなたのことを知らないし、あなたが何に困っているかも知らない。フレームワークは便利だが、リスクを認識しておく必要がある。

  • フレームワークのアーキテクチャに難があることが少なくない
    • ビジネスオブジェクト(エンティティ)にフレームワークを結合させるなんてとんでもない!
  • プロダクトが成長するにつれて、フレームワークの提供する機能では手に負えなくなってくる
    • フレームワークと密結合していると、フレームワークに邪魔ばかりされることになる
  • フレームワークの進化の方向が、あなたの望む道からずれ、無用なコストがかかる場合がある
    • なんの利益にもならない新バージョンへのアップグレード対応
    • 便利に使っていた機能の突然の廃止や仕様変更

フレームワークを使うことは問題ない。ただし、フレームワークとは一定の距離を保ち、結合しないことが大切だ。フレームワークをコアのコードに混ぜないこと。フレームワークなんかと結婚するな!

書き残したこと

いくらうまい設計をしても、その実装方法の複雑さを考慮しなければ、あっという間に設計が崩れてしまう。理想に走りすぎてはいけない。チームの規模やメンバーのスキル、ソリューションの複雑さ、時間と予算の制約などを考慮しよう。


あわせて読みたい

本書にはまったく参考文献が載っていないので、独断と偏見であわせて読みたい書籍をあげておく。

アプリケーションアーキテクチャ

Clean Architecture」はやや抽象度が高い。より具体的なアーキテクチャ本として「.NETのエンタープライズアプリケーションアーキテクチャ」をオススメしたい。「関心事の分離」「DRY」「SOLID原則」などの設計原則からはじまり、「ドメイン駆動設計」「CQRS」「イベントソーシング」など幅広い内容が、詳しく紹介されている。

ビジネスルール

第5部ではひたすら「ビジネスルールを技術的詳細から分離せよ」と強調される。一方で分離したビジネスルール自体の設計については、ほぼ触れられていない。ビジネスルールの設計については、「エリック・エヴァンスのドメイン駆動設計」や「実践ドメイン駆動設計」が良いガイドになる。また、「ユースケース駆動開発実践ガイド」ではドメインモデル(本書でいうエンティティ)やユースケースのモデリング技法が紹介されており、前半だけでも読む価値がある。

API設計

優れたコンポーネントはAPIもエレガントである。優れたコンポーネントを設計するために、ぜひともAPI設計について学ぼう。「APIデザインの極意」と「C++のためのAPIデザイン」はAPI設計をテーマにした意欲作である。

オブジェクト指向

オブジェクト指向の実践的な設計については「現場で役立つシステム設計の原則」が読みやすくてオススメ。SOLID原則は「アジャイルソフトウェア開発の奥義」や「Adaptive Code」が詳しい。

The Clean Architecture

「Clean Architecture」という書名にも関わらず、あの有名な同心円が出てくるのは後半の22章である。しかも、この書籍の中では、ぶっちゃけ脇役だ。そのため、本記事からもバッサリ削っている。内容は「クリーンアーキテクチャ(The Clean Architecture翻訳)」ほぼそのままなので、気になる人はリンク先を参照してほしい。


  1. 変数の値を変更する手段が用意された関数型言語も多いが、その手段は非常に厳しく制限されている。