Clean Architectureの個人的まとめ

はじめに

時々名前が挙がる本、Clean Architecture読みました。
定期的に内容を思い出せるようにまとめてみますが、個人的解釈なども含まれるため、正確なことを知りたい方本を買ってください。

基本的に以下の流れでまとめます


個人的なまとめ。そのまま引用していない部分もあります。

感想など


前提

アーキテクチャは問題を解決する手法をまとめているのではなく、開発や保守のコストを下げることが目的です。問題解決の方はデザインパターンと呼ばれます。

また、この本に書いてあることが全て正しいという話ではなく、その時の規模などに合わせて調整することが正しいみたいです。

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

1章 設計とアーキテクチャ

崩壊のサインはコードの変更に莫大なコストがかかっていること。
きちんとアーキテクチャと向き合い、遅くとも着実に成し遂げる方が良い。

多少回り道になっても少しの変更に時間がかかっていることを感じたら、すでにヤバいみたいです。アーキテクチャをしっかり意識しましょう。

2章 2つの価値

設計とアーキテクチャは同じもの。
振る舞いとアーキテクチャのどちらが大切かを考える。アイゼンハワーのマトリックスに当てはめて考えると、まず振る舞いは緊急であり、アーキテクチャは重要と分類できる。この場合「重要でないが緊急」と「緊急でないが重要」を比べると、緊急でないが重要である事柄の優先順位が高く、アーキテクチャの方が大切

謎理論にも聞こえるが、とにかくその場しのぎの実装で重要でない部分を実装するならそれよりもアーキテクチャを意識しましょうってことみたい。
もちろん「緊急で重要」が一番の優先解決事項

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

3章 パラダイム

3つのパラダイム、「構造化プログラミング」「オブジェクト指向プログラミング」「関数型プログラミング」がある。それぞれgoto,関数ポインタ,代入をすべきでないと伝えている。

何をすべきでないかを伝えることで生成されるコードを制御している。
一旦この話は飛ばしていいと思う

4章 構造化プログラミング

「機能分割」のためにgoto文はうまく使わなければならない。プログラムは順次、選択、反復(、関節参照)の3要素のみで生成できる。機能分割の目的はテストのしやすい状態を保ちたい。

機能を分割して、テストのしやすさを上げる→いくつものテストを行うことでそのプログラムの信用性を上げることを重要としている。
かなり前から機能分割が良いとされているらしいし、テストの観点から機能分割されていることは正しいと感じる。

5章 オブジェクト指向プログラミング

オブジェクト指向=OO言語とする
OO言語の特徴は基本的にカプセル化、継承、ポリモーフィズムができることだそう
カプセル化自体はCでも可能。むしろC++やC#になるにつれキーワード必須のカプセル化になっているため弱体化している。
継承はCでも一応可能だが、キャストの明示などがあり不便である。OO言語などの方が優れている。
ポリモーフィズムは関数ポインタの応用であり、Cでも可能。だがOO言語の方が優れている。
OOとは「ポリモーフィズムを使用することで、システムにあるすべてのソースコードの依存関係を絶対的に制御する能力」である。これによりプラグインアーキテクチャを作成できる。

OO言語にはどのような利点があり、実際Cと比べどのように違うのかが書かれています。特にポリモーフィズムがOOを支えてるみたいな雰囲気があり、依存関係の逆転によるモジュール間の疎結合が強いみたいです。

6章 関数型プログラミング

関数型プログラミングは変数の変更ができない。これにより競合やデッドロックなどの並列系の問題が発生しない。不変コンポーネントにできるだけ処理を押し込むべき。
イベントソーシング=状態ではなく取引のみ記録し、記録をたどって現在の情報を求める。変更が存在しない

初めて聞いた考えでした。確かに代入による値の変化がないという前提があればかなりシンプルな構造にすることができそうです。もちろん全ての代入を制限は難しいですが、一部のコンポーネントにその規制を押し込むことはできそうです。

第Ⅲ部 設計の原則

SOLID原則などの説明がここから始まる。

SOLIDの目的は以下の3つ
・変更に強い
・理解しやすい
・コンポーネントの基盤として、多くのソフトウェアシステムで利用できること

7章 SRP 単一責任の原則

”必ず”一つの関数が一つの機能のみ行うべきではない。
モジュールは変更を望む人たちに(アクター)対して責務を負うべき。
不必要に同じ動きをするコードをまとめると、変更した際に想定していない人が使っていたなんてこともある。別々のチームが同じコードを使うことは避けた方が良い。

モジュール=クラスぐらいの感覚
”必ず”一つの関数が一つの機能のみ行うべきではない。の部分はケースバイケースなので意識しすぎる必要はない意味でいれました。
同じコードがある場合にまとめることは良いことの方が多いですが、たまたま同じコードを別の用途で使っている可能性があるため、修正をしない方が良い場合もあるようです。
時には複数のクラスに一つづつ関数を持たせて、それらを持つクラスが存在するといった形もあるらしい。

8章 OCP オープン。クローズドの原則

ソフトウェアの振る舞いは既存の成果物を変更せず拡張できるようにすべき。
インターフェイスの内部を知りすぎると推移的な依存関係となってしまう。「ソフトウェアのエンティティは自分が直接使っていないものに依存すべきではない」

なかなか難しそうな部分にはなります。拡張することによって既存のコードが変更されない状態は理想的なクリーンアーキテクチャに一歩近づけそうです。
そのために必要なものはSRPによって処理を分割し、DIPによって依存関係をまとめることだそうです

9章 LSP リスコフの置換原則

あるクラスを継承元のクラスに入れ替えても役割を果たすこと、継承後に機能を破壊しない

そもそも継承を多用することは避けるべきって話は他の書籍でもよく見ます。もし使用する場合は関数をオーバーライドしてまったく異なる動きにさせないよう注意する。もし動きが変わるならその継承関係は正しくないと考えられそうです。

10章 ISP インターフェイス分離の原則

不必要な関数を持つインターフェイスにするのではなく、複数のインターフェイスを持つクラスとかを作りなさい。影響範囲の広がりを抑える効果もある

機能もりもりのインターフェイスではなく適切に分割しなさいとのことです。

11章 DIP 依存関係逆転の原則

ソースコードの依存関係が抽象だけを参照しているものが最も柔軟なシステムである。ただし、OSやプラットフォーム周りは変更が少ないので気にしなくてよい。
変化しやすい具象を継承、参照、や具象関数のオーバーライドをすべきでない。ただしmain関数のような部分では避けようがない場面がある

この本でよく出てきました。
とにかく依存関係にあるものに対してインターフェイスを挟むことで具象(具体的な実装)ではなく抽象(インターフェイスで定義された関数)のみを参照することで柔軟なシステムになると言っているようです。
とはいえインターフェイスの変更もいくらでもできるのではと思ってしまいます。おそらくインターフェイスの変更はそれを実装するものに振り回されるべきではない前提の話だと思います。

”依存関係逆転”と呼ばれていますが、基本的にインターフェイスを利用した疎結合のみが利点となります。
あまり逆転感はありませんが、インターフェイスが依存する側のモジュールに存在するため、依存される側の実装が依存する側のモジュールに依存しているとして、”依存関係逆転”となるようです。名前の語源は考えない方が楽だと思います・・・。

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

12章 コンポーネント

コンポーネント→複数のクラスの集まり。UnityのADFのような認識。

あまり書く内容がありませんでした。
少し低レイヤーな内容でクリーンアーキテクチャとの関わりは薄そうです。

13章 コンポーネントの凝縮性

REP 再利用・リリース等価の原則
リリースする機能にはバージョンを付けると使用可能なバージョンがすぐ分かるため再利用しやすくなる。また、まとめてリリース可能でなくてはならない。同じバージョン番号を共有し、同じリリースドキュメントを持つこと

CCP 閉鎖性共通の原則
SRPをコンポーネントに言い換えたもの。SRP「クラスを変更する理由は複数あるべきでない」、CCP「コンポーネントを変更する理由が複数あるべきでない」。
同じタイミングで変更されることが多いクラスは一つにまとめておけ

CRP 全再利用の原則
依存関係の強いものは同じコンポーネントとすべき。
同一コンポーネント内に依存するクラスもあれば依存しないクラスもある状態は避けたい

CRPはREP,CCPと相反する内容ではあるが、ちょうど良いところに落とし込むことが技量となる

SOLIDのような考え方がコンポーネント単位でも行われるそうです。
基本的にコンポーネントを分ける意識をしたことはないですが、REP、CCPならなんとなく実践できそうです。3つをうまく使うには経験が必要なようです。

14章 コンポーネントの結合

ADP 非循環依存関係の原則
循環依存している場合は一つのクラスの修正がその循環全てに影響してしまう。避ける方法として、DIPや循環から2つのクラスに依存するクラスを新たに作る方法がある。(トップダウンになっていない部分に新たにクラスを作り、2つのトップダウンの流れができるようするイメージ?)
コンポーネントの依存関係はシステムの論理設計に合わせて育てていく

SDP 安定依存の原則
変動を想定したコンポーネントは変更しづらいコンポーネントから依存されてはいけない。
安定なコンポーネントは複数のコンポーネントから依存されている状態(変更しづらい)。
ファンイン・アウトから安定度を算出した際、コンポーネントの依存性の方向に従って小さくなるべき(末端にいくにつれて依存されやすくなる。依存されまくっているものが変更しやすいものに依存すべきではない)

SAP 安定度・抽象度透過の原則
安定度の高いコンポーネントは抽象度も高くあるべき。安定度の高さが拡張の妨げになってはいけない
安定度が高くなる方向に依存すべき。高い抽象度に対し、依存するコンポーネントが少なすぎる場合は無駄になることもある。

ADPでは循環参照をすべきでないと言っています。クラス単位でも循環参照は避けるべきと言われています。この利点として、最も末端の修正は他のクラスに影響を及ぼさないことが挙げられるようです。
SDP、SAPにおいて、依存関係をカウントすることで現在の安定度などの指標(A・I・Dなどの名前が付けられています)を算出することが可能。問題がでれば間にインターフェイスを挟むことで解決することもあるようです。

15章 アーキテクチャ

アーキテクチャの目的はシステムを適切に動作させることではなく開発・デプロイ・運用・保守を容易に行うこと
優れたアーキテクチャは方針と詳細を区別できる

アーキテクチャの目的を再度挙げています。
方針と詳細については、目的と手段が入れ替わることのないように区別されているべきということだと思います。

16章 独立性

よいアーキテクチャは選択肢を残しておく(レイヤーの切り離しなど)。大規模なデプロイと小規模なデプロイで切り離しのレべルを切り替えられるように。
UIとデータベースが分離されていれば異なるサーバーで実行できる、独立した開発も可能
切り離しのレベルにソース・デプロイ・サービスがあるが、プロジェクト初期では判断できない。そのため決定を先延ばしにして情報をあつめ、決定していく方が良い。先延ばしにしている間に決めていくべきなのはビジネス要件

私は考えたことのないものでした。
選択肢を残しておくの部分については、方針が決まり実装段階に入っても依存関係の分離ができていれば別々に開発できる部分がある。進めていくうちに初期に必要だと考えていたものが不必要になることがあるため無駄を省ける。ってことのようです。(下でまた説明します)
悪い例として、最初から特定のDBに依存した開発が、途中でそのDBだと過剰であると気づいた際に別のDBに切り替えられないということだと思います(DBをよく理解しているわけではありませんが)。きちんと分離ができていればどのようなDBでも接続部分のインターフェイスの実装のみで終わるはずなのでベタベタに依存した状態も避けられ、そもそもそれが必要かどうかも後から分かることができます。それまではモックで対応可能な部分まで実装を進めるんだと思います。

17章 バウンダリー:境界線を引く

ソフトウェアアーキテクチャは境界線を引く技法→バウンダリーと呼んでいる
早すぎる決定と結合はコストがかかる結果を生む。実装を後回しにできるもの(モックを使って開発できるもの)は後まわしにしておくと開発中に必要でないものが出た際に捨てやすくなる
GUI周りは境界線となりやすい
ビジネスルール外との連携はプラグイン的にするべき(ビジネスルールとなるコンポーネントが存在するらしい)
境界線は変更の軸があるところに引く。境界線の外側の変更を意識しなくていいから。

SRP的な話になります。
ビジネスルールの実装とGUIの実装はほぼ確実に分離されるべきなようです。確かにMVCパターンなどでも表示の部分とは分離することが求められますので、特に違和感はないですがコンポーネント単位でも境界を引くべきなようです。
ここの境界線は人によって認識の差が出てきそうです。

18章 境界の解剖学

下位レベルのサービスは上位レベルのサービスにプラグインされるべき
モノリス以外のほとんどのシステムでは複数の境界戦略を使用する。システムの境界はローカルでにぎやかな境界とレイテンシーに影響される境界がある

モノリスは単一実行ファイルの認識。
下位レベルのサービスは上位レベルのサービスにプラグインされるとは、上位レベルからみて下位レベルがツールの一つであること(下位レベルの実装が変わっても上位レベルからは関係がない)だと思います。
システムの境界はスレッド(ローカルでにぎやか)やWebサーバー(レイテンシーが影響する)など。

19章 方針とレベル

レベル→入出力からの距離
データフローとソースコードの依存性は必ずしも同じ方向を向いていない

上位レベルは下位レベルに依存すべきではない。例えば文字の出力と文字の整形をするクラスがある場合、文字の整形をするクラスは文字の出力方法を知る必要はないため、インターフェイスを使って逆転させた方が良い。

20章 ビジネスルール

ビジネスルールはビジネスマネーを生み出したり節約するルールや手続き。プログラムであることは考えず、手作業でもできるものもビジネスルールとなる。
最重要ビジネスルールと最重要ビジネスデータはエンティティと呼ばれる
ユースケース=自動化されたシステムを使用する方法を記述したもの。ユーザーとエンティティのインタラクションを支配する。
依存性逆転によりエンティティはユースケースのことは知らないが、ユースケースはエンティティを知っている。
ユースケースはユーザーや他のコンポーネントとのデータ通信などには触れるべきではない。依存性がないことが重要

なかなか独学のプログラムでビジネスルールを意識することが少ない気がします。このビジネスルールは詳細によって変化するべきものではないはずです。

21章 叫ぶアーキテクチャ

フレームワークはツールである。システムの提供方法が分からなくとも、ユースケースは全て把握できるようにすべき(詳細は後回し)

叫んでいるの感覚が分からないですが、初めてそのコードを読んだ人がどのようなプログラムであるかを分かりやすくすべきということみたい。ユースケースが分離されていればそのようになる気もする

22章 クリーンアーキテクチャ

アーキテクチャの特徴
・フレームワーク非依存
・テスト可能
・UI非依存
・データベース非依存
・外部エージェント非依存
クリーンアーキテクチャは4層から表され、内からエンティティ・ユースケース・インターフェイスアダプター・フレームワークとドライバとなっている。中央の抽象度の高い方へのみ依存の方向が向いている。
内側は外側のことを知らないし、外側が内側のコードに触れるべきでない。
境界線を越えるデータは独立した単純なデータ構造であることが重要

ようやくクリーンアーキテクチャの話。
アーキテクチャの特徴の部分は基本的に今までの考えをふまえれば自然と得られるメリットに見えます。
エンティティは何かに依存すべきでないし、ユースケースはエンティティに依存してもインターフェイスアダプターには依存すべきではない。
これでユースケースと詳細な実装が区別できそうな気がします。

ここでは4層としていますが、場合によるかと思います。

23章 プレゼンターとHumbleObject

テストのしにくいHumbleとそれ以外にモジュールやクラスに分ける。
ViewはHumbleなのでテストはしづらい部類
Presenterはテスト可能。(数値を入れ、負の数であればフラグを設定するなどを行う部分)

GUIなどに関してはユニットテストが難しいため、テストが容易な部分とは分離すべきとのことです。GUIを分離するのは良いとしてもそれ以外も分離するのは少し違和感がでてきそうです。ですが、この境界がアーキテクチャの境界の定義に繋がるそうです。
クラスやモジュールの責任としてテストができるものだけで構成されたものを作っておいた方が、変更後のモジュール全体の確認テストを早期に終わらせることができるという考え?

24章 部分的な境界

本来境界には相互にインターフェイスを用いるべきであるが、コストがかかる
Strategyパターン→インターフェースを片方のみ扱う。
Facadeパターン→境界にFacadeクラスを定義し、そのクラスを経由して本来インターフェイスを介してアクセスする箇所へアクセスを行う
上2つは好ましくないパターンだが、コストなどを考えた際、一時的に採用してもよいらしい

ここでは最初からクリーンアーキテクチャを目指してもコストが見合わない場合があるため、一時的に簡易なものを使用してもいいよみたいな話をしています。

25章 レイヤーと境界

アーキテクチャの境界はあらゆるところに存在し、常に境界部分を見張る必要がある

あまりこの章での主張は分からないですが、機能の追加によってアーキテクチャの境界は変わるため定期的に確認しましょうと言いたいよう。

26章 メインコンポーネント

メインコンポーネント→Main関数となるようなコンポーネント。
これは初期状態や状態の設定、外部リソースの収集、上位レベルの方針に制御を渡している

Mainをアプリケーションのプラグインとできると開発用や本番用などのMainを用意することができる。
わざわざMainとして章を分ける必要は分からない。

27章 サービス:あらゆる存在

サービスがアーキテクチャを定義するわけではない
サービスのメリットはサービス間が強く分離されていることとされている。変数レベルでの分離は行われているが、データ(の形式)と強く結びついているため、このメリットは幻想である。
また、他のメリットとして専属のチームがサービスの所有・運用することだと言われる。開発とでデプロイの独立性はスケーラブルといわれるが、サービスでしか行えないわけではない
アーキテクチャの境界はサービス間に位置していない。アーキテクチャの境界はサービスを横断することでコンポーネントに分割している

特になし

28章 テスト境界

アーキテクチャから見ると全てのテストは同じである。
共通システムコンポーネントによって多くのテストが壊れることを脆弱なテスト問題と呼ぶ。GUIなどの変化しやすいもののテストスイートは脆弱。GUIを使用せずにテストできることが望ましい。
テストAPIの目的はアプリケーションの構造からテストを切り離すこと、
テストAPIの役割はアプリケーションの構造をテストから隠すこと

特になし

29章 クリーン組み込みアーキテクチャ

ソフトウェアは消耗しないが、管理できていないハードウェアなどの依存関係により 破壊される可能性がある。
(これは組み込みに置ける話のみかも)ソフトウェアとファームウェア(PCでいうとこのBIOS)は分離されているべき、間にHAL(ハードウェア抽象化レイヤー)を挟むことになる(PCでいうとこのOS)また、さらにOSとソフトウェアの間に入るOSALがある。
OSALによってOSなしのテストや、OSに依存しない(間のOSALが頑張るんだけども)ソフトウェアを作れる。

組み込みに関しては特に取り上げない

30~32章 データベース・Web。フレームワークは詳細

詳細であればアーキテクチャに関わるものではない。
データベースが何であれ欲しいデータの取得のみの機能であれば詳細となる
WebはGUIだから詳細であると考えられる。
フレームワークも詳細である。

どれにおいても詳細である。これらが変わったからといってビジネスルールやユースケースが変わるものではない。
とはいえC++のSTLなどは分離がかなり難しいためあきらめて良い

33章 事例

特になし

34章 書き残したこと

publicを使いすぎないよう注意してという話。publicを使いすぎると意識しないうちにコンポーネント間を飛び越えた参照を持つことになりかねない。コンパイラをうまくつかって 結合しないように制御もできる。


全体を通しての感想

SOLIDの原則は意識した上で特に、依存関係間にインターフェイスを挟むことで疎結合にし、詳細を意識しないで済むことを目指すようでした。
全く設計を意識したことのない人が呼んでもかなり難しいかと思います。特に依存の流れを1方向にしようとする意識と、SOLIDの原則の理解は必要がありそうでした。

できれば各章のまとめがもう少し具体的なまとめであって欲しかったです。

また、これだけで完全にクリーンアーキテクチャを作り出すことは無理だと思います。実際の事例に当てはめてみると曖昧な部分が数多く存在すると思うので、しっかりと時間を取って今の状態を確認したり考える時間は必須なようです。
特にアーキテクチャの特徴である以下の5つは忘れないようにしたいです。
・フレームワーク非依存
・テスト可能
・UI非依存
・データベース非依存
・外部エージェント非依存

少し話はそれますが、PCのKindleだと画像が表示されないことがほとんどでした。スマホからであれば画像の確認できるので両方を確認する必要がありました。

この記事が気に入ったらサポートをしてみませんか?