覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

「Clean Architecture 達人に学ぶソフトウェアの構造と設計」

かなり前に読み終えてはいたんですが、「Clean Architecture 達人に学ぶソフトウェアの構造と設計」の内容に関する今更ながらの自分用のメモです。

結構話題になった本だと思います。自分にとっても結構役に立つ内容が多かったです。
(ただ、まだ若干理解しきれない部分もありましたが。。。)


書籍の内容紹介には以下のように書かれています

アーキテクチャのルールはどれも同じである!

書いているコードが変わらないのだから、どんな種類のシステムでもソフトウェアアーキテクチャのルールは同じ。
ソフトウェアアーキテクチャのルールとは、プログラムの構成要素をどのように組み立てるかのルールである。
構成要素は普遍的で変わらないのだから、それらを組み立てるルールもまた、普遍的で変わらないのである。
(本書「序文」より)


この本は、Clean Architecture という考え方に言及したのものですが、具体的な実装方法については記述されていない点には注意が必要です。
あくまで概念的な説明や抽象的な内容で、大規模ソフトウェア(長期的に運用するアプリケーション)を設計する際に気を付けなければならないポイントに焦点を当てて解説してくれています。
(※パッケージ構成やクラス間の依存関係について説明のある章はあります)

そのため、プログラムの初学者や未経験者が読むには適していません。
一定の経験を積んだソフトウェア開発者が読むのが良いと思われます。
プログラム初学者がいきなり手を出すと、読んでいる最中に眠くなるんじゃないかな・・と思います。


章立ては以下のようになっています。

  • 第1章: 設計とアーキテクチャ
  • 第2章: 2つの価値のお話
  • 第3章: パラダイムの概要
  • 第4章: 構造化プログラミング
  • 第5章: オブジェクト指向プログラミング
  • 第6章: 関数型プログラミング
  • 第7章: SRP : 単一責任の原則
  • 第8章: OCP : オープン・クローズドの原則
  • 第9章: LSP : リスコフの置換原則
  • 第10章: ISP : インターフェィス分離の原則
  • 第11章: DIP : 依存性関係逆転の原則
  • 第12章: コンポーンネント
  • 第13章: コンポーネントの凝集性
  • 第14章: コンポーネントの結合
  • 第15章 : アーキテクチャとは?
  • 第16章 : 独立性
  • 第17章 : バウンダリー:境界線を引く
  • 第18章 : 協会の解剖学
  • 第19章 : 方針とレベル
  • 第20章 : ビジネスルール
  • 第21章 : 叫ぶアーキテクチャ
  • 第22章 : クリーンアーキテクチャ
  • 第23章 : プレゼンターとHumble Object
  • 第24章 : 部分的な境界
  • 第25章 : レイヤーと境界
  • 第26章 : メインコンポーネント
  • 第27章 : サービス:あらゆる存在
  • 第28章 : テスト境界
  • 第29章 : クリーン組込みアーキテクチャ
  • 第30章 : データベースは詳細
  • 第31章 : ウェブは詳細
  • 第32章 : フレームワークは詳細
  • 第33章 : 事例:動画販売サイト
  • 第34章 : 書き残したこと

各章は、内部的にさらに細かな章で構成されています。
その最も細かく分類された章1つ1つをとって見ると別の書籍やウェブサイトですでに一般的に語られている内容が書かれていることも少なくないです。
この本は、基本的には本全体を通して読む必要があると思います。が、各章単体で見ても、ソフトウェア設計における重要な要素を示唆している場合もあります。

個人的には、第21章の「叫ぶアーキテクチャ」が好きです。
この章はものすごく短い内容ですが、大規模ソフトウェア(システム・サービス・アプリケーション)で、これが出来ていない、または、修正を繰り返すうちに、これが出来なくなっているというケースが少なくないなーという重要な指摘がされています。

他にも 第32章 : フレームワークは詳細 なども面白いです。


各章の詳細は、この本を読んでもらって把握してもらいたいのですが、
本書籍内で、私が気になった部分の抜粋を一部メモしておきます。(括弧部分は私の補足です)


  • ソフトウェアアーキテクチャの目的は、求められるシステムを構築・保守するために必要な人材を最小限に抑えることである
  • 設計の品質は、顧客のニーズを満たすために必要な労力で計測できる。必要な労力が少なく、システムのライフタイム全体で低く保たれているならば、その設計は優れている。逆にシステムごとに労力が増えるなら、その設計は優れていない。
  • 「あとでクリーンにすればいいよ。先に市場に出さなければ!」
    開発者たちはそうやっていつもごまかす。だが、あとでクリーンにすることはない。市場からのプレッシャーは止まらないからだ。「先に市場に出さなければ」ということは、後ろに競合他社が大勢いるということである。競合他社に追い抜かれないためには、これからも走り続けるしかない。
  • 崩壊したコードを書くほうがクリーンなコードを書くよりも常に遅い。
  • ソフトウェアはソフトでなければならない。つまり、簡単に変更できなければならない。ステークホルダーが機能を変更したいと思えば、その変更は簡単にできるようになっておくべきだ。変更の難易度は、変更の形状ではなく、変更スコープに比例しなければならない。
  • ビジネスマネージャーに変更を希望するかと聞くと、当然ながら「希望する」と答える。ただし、将来の柔軟性よりも現在の機能性の方が重要だと回答を補足するはずだ。とはいえ、ビジネスマネージャーが変更を求めたとき、あながた膨大なコストを見積もると、変更が非現実的になるまでシステムを放置したあなたに対して、ビジネスマネージャーは激怒するだろう。
  • これらのパラダイム(構造化プログラミング、オブジェクト指向プログラミング、関数型プログラミング)は、プログラマから能力を削除しているのである。新しい能力を提供しているものはない。それぞれがネガティブな意図を持ち、何らかの規律を課しているのである。これらのパラダイムは、何をすべきかを伝えるというよりも、何をすべきでないかを伝えているのである。
  • ソフトウェア開発は、数学的な構成要素を操作しているかのように思えるかもしれないが、実際には数学的な試みではない。むしろソフトウェア開発は科学のようなものである。どれだけ最善を尽くしても正しくないことを証明できないことによって、その正しさを明らかにしているのである。
  • 確かにOO(オブジェクト指向)は、プログラマがカプセル化されたデータを盗み見ないように、礼儀正しく振舞うべきであるかという考え方に依存している。だとしても、OOを提供すると主張している言語は、C言語で完ぺきに実現できているカプセル化を弱体化させてしまっているのである。
  • ポリモーフィズムは関数へのポインタの応用である。1940年代後半にノイマン型アーキテクチャが実装されて以来、プログラマはポリモーフィズムの振る舞いを実現するために、関数へのポインタを使用してきた。つまり、OO(オブジェクト指向)は新しいものを提供しているわけではないのである。
  • アーキテクチャの観点からすると、なぜこれ(不変性)が重要なのだろうか?なぜアーキテクトは、変数の可変性に配慮すべきなのだろうか?その答えは、簡単すぎるぐらいである。それは、競合状態、デッドロック状態、並行更新の問題の原因が、すべて可変変数にあるからだ。変数が変化しないのであれば、競合状態や並行更新の問題は発生しない。また、変更可能なロックがなければ、デッドロックになることもない。
  • モジュールはたったひとつのアクターに対して責務を負うべきである。
  • 変化しやすい具象クラスを継承しない。具象関数をオーバーライドしない。
  • 最終的にどのような形式でデプロイするかにかかわらず、よくできたコンポーネントは常に個別にデプロイできる状態を保っているため、個別に開発を進められる。
  • 多くのアプリケーションにおいて、再利用性よりも保守性の方が重要だ。アプリケーションコードが変更しなければならないときには、ひとつのコンポーネントに変更箇所がまとまっているほうが、あちこちのコンポーネントに散らばっているよりもありがたい。変更箇所がひとつのコンポーネントに閉じていれば、変更後にデプロイする必要があるのはそのコンポーネントだけになる。そのコンポーネントに依存していないコンポーネントは再デプロイする必要がない。
  • コンポーネントの依存構造をきちんと管理しておく必要がある。循環依存があってはいけない。
  • 循環依存があると、コンポーネントを切り離すのが難しくなる。ユニットテストやリリースも難しくなり、失敗に陥りやすくなる。さらに、モジュールの数が増えるにつれて、ビルド時の課題も幾何級数的に増加する。
  • アーキテクチャの主な目的は、システムのライフサイクルをサポートすることである。優れたアーキテクチャがあれば、システムを容易に理解・開発・保守・デプロイできる。最終的な目的は、システムのライフタイムコストを最小限に抑え、プログラマの生産性を最大にすることである。
  • ソフトウェアシステムが効果を生み出すためには、デプロイ可能な状態でなければいけない。デプロイのコストが高ければ、どの分だけシステムの有用性は低下する。ソフトウェアアーキテクチャの目的、システムを単一のアクションで簡単にデプロイできるようにすることである。
  • ソフトウェアシステムのすべてにおいて、保守は最もコストがかかるものである。永遠に続く新しい機能の登場と、避けられない欠陥や修正の繰り返しは、人的リソースを膨大に消費する。...
    アーキテクチャを慎重に考え抜けば、これらのコストは大幅に低下する。システムをコンポーネントに分離して、安定したインターフェイスを持つ独立したコンポーネントにしておけば、将来の機能の道筋が照らされ、意図せずに壊してしまうリスクを大幅に低減できるだろう。
  • 明らかに重複しているコードが異なる進化を遂げ、数年後には両者がまるで違ったものになっていることもある。これは本物の重複ではない。偽物の重複、あるいは偶然の重複である。
  • レイヤーを水平に分離していると、あるデータベースのレコードのデータ構造が、ある画面のデータ構造とよく似ていることに気づくことがある。ビューモデルを作成して要素をコピーするのではなく、データベースのレコードをそのままUIに渡したくなるが、注意してほしい。これは確実に偶然の重複である。ビューモデルを作成するのは大した労力ではないし、レイヤーを適切に切り離すのに役立つだろう。
  • 早すぎる決定とはどのようなものなのか?それは、システムのビジネス要件(ユースケース)と関係のない決定である、たとえば、フレームワーク、データベース、ウェブサーバ、ユーティリティライブラリ、DIなどに関する決定が含まれる。優れたシステムアーキテクチャは、このような決定を従属的かつ遅延可能なものにする。優れたアーキテクチャは、このような決定に依存しない。優れたシステムアーキテクチャは、重大な影響を与えることなく、このような決定を最終時点まで引き延ばせる。
  • アーキテクチャの目標は、下位レベルのプロセスを上位レベルのプロセスのプラグインにすることである。
  • ユースケースとは、自動化されたシステムを使用する方法を記述したものである。ユーザから提供された入力、ユーザに戻す出力、出力を生成する処理ステップなどを規定している。エンティティに含まれる最重要ビジネスルールとは違い、ユースケースにはアプリケーション固有のビジネスルールを記述している。
  • あなたのアプリケーションのアーキテクチャは何と叫んでいるだろうか?最上位レベルのディレクトリ構造と最上位レベルのパッケージのソースファイルは、「ヘルスケアシステム」「会計システム」「在庫管理システム」と叫んでいるだろうか?それとも「Rails」 「Spring/Hibernate」「ASP」と叫んでいるだろうか?
  • アーキテクチャはフレームワークに関するものではない。アーキテクチャはフレームワークから提供されるものではない。フレームワークは使用するツールであり、アーキテクチャが従うものではない。あなたのアーキテクチャがフレームワークにもとづいているのなら、そのアーキテクチャはユースケースにもとづくことはできない。
  • アーキテクチャは、システムで使用しているフレームワークではなく、システムそのものについての情報を伝える必要がある。たとえば、ヘルスケアシステムを構築しているのであれば、新しく参加したプログラマがソースリポジトリを見たときに、「ああ、これはヘルスケアシステムだ!」と思えるようにしておくべきである。システムの提供方法はまだわからなくても、システムのユースケースを全て把握できるようにしておくべきだ。
  • Mainコンポーネントは、究極的な詳細である。システムの最初のエントリポイントとなる。オペレーティングシステム以外に、このコンポーネントに依存しているもはない。FactoryやStrategyなどのグローバルな要素を作成し、システムの上位の抽象部分に制御を渡すことが、このコンポーネントの仕事になる。
    依存関係については、このMainコンポーネントにDIフレームワークを使って注入する必要がある。Mainに注入できたら、あとはフレームワークを使用せずに、Mainが通常のやり方で依存関係をちりばめる。
    Mainは、汚れ仕事が最も似合うコンポーネントだ。
  • ソフトウェア設計の第一のルールは、その理由がテスト容易性だろうと何だろうと、常に同じである。それは、変化しやすいものに依存しないだ。GUIは変化しやすい。GUIを使用してシステムを操作するテストスイートは脆弱である。したがって、GUIを使用しなくてもビジネスルールのテストができるように、システムとテストを設計しなければならない。
  • データ構造であるデータモデルは、アーキテクチャ的には重要である。だが、回転する磁気面上でデータを移動させる技術やシステムは、アーキテクチャ的には重要ではない。リレーショナルデータベースシステムは、データを表形式で管理して、SQLからアクセスするための仕組みであり、これはデータモデルよりも技術やシステムに近い。データこそが重要なのであり、データベースは詳細なのである。
  • (フレームワークの)作者にとって、フレームワークとの結合には何のリスクもない。むしろフレームワークとの結合を望んでいる。作者自身はそのフレームワークを好きなようにコントロールできるからだ。
    さらに作者は、あなたに対してもフレームワークとの結合を望んでいる。一度でも結合してしまえば、切り離すのはとてもむずかしくなるからだ。フレームワークの作者にとって、自分の作った基底クラスを大勢のユーザが継承することほど、自尊心が満たされることはない。

このメモをは自分用のものなので、これだけ読んでも何のこっちゃ という感じだと思いますので、
ぜひぜひ書籍を手にとって読んでもらいたいです。



関連リンク