cover|150

Highlight

所感

ソフトウェアを「ソフト」に、柔軟性を保つためのアーキテクチャの考え方が書かれた本です。

いろんなところでも言われているように、ボブおじさんの元ネタ記事以上のことは書かれていません。

しかし、SOLID原則やコンポーネントの安定度などの解説があってからクリーンアーキテクチャの具体的な解説に入るので、元ネタの記事よりもすんなりと受け入れることができました。(ところどころの章にあるソフトウェア昔話も面白かったです。)

表紙にある「アーキテクチャのルールはどれも同じである」の『ルール』とは、「適切に境界線を引いて、方針が詳細に依存しないように作る」ということだと解釈しました。

オープン・クローズドの原則やコンポーネントの安定度を考慮して適切に境界線を引き、依存関係逆転の法則、リスコフの置換原則を適用して方針が詳細に依存しないようインターフェイスを設けることでソフトウェアをソフトな状態に保てます。

ただ、私自身Javaを書いた経験がないため、コンポーネントによるパッケージングといった話はいまいちピンと来ませんでした…。

また、フロント周り(_ViewがViewModelを見つける_方法など)への言及が少なめのため、実際どうやってJSフレームワークを使ってSPAを構築していくかは頭に浮かびづらいと感じました。この辺は実際にやっている人もいるので、別途参考にしていこうと思います。

まとめ

第Ⅰ部

  • ソフトウェアアーキテクチャの目的は、システム構築・運用に必要な人材コストを最小限に抑えるためである。
    • システムを急いでリリースすると、だんだんとコード一行あたりのコストも膨れ上がっていく。
    • 「急いでリリースして、あとでクリーンにする」ことなど不可能。
    • 実は短期的にも長期的にも、崩壊したコードを書く方が、クリーンなコードを書くより遅い。
      • TDDで開発したほうが10%早いというデータがある
  • ソフトウェアには「ソフト =アーキテクチャ」と「ウェア =機能」という2つの価値がある。
    • どっちかが大事じゃなくて、どっちも大事。
    • アーキテクチャを後回しにしていると、変更困難なシステムになってしまう。

第Ⅱ部

オブジェクト指向プログラミングのメリット

第Ⅲ部 設計の原則

SOLID原則

  • SOLID原則とは、関数やデータをどうクラスに組み込み、クラス同士をどう接続させるかの指針になる。
  • SOLID原則は、以下の3つの特徴を持ったソフトウェア構造を作るという目的がある。
    • 変更に強いこと
    • 理解しやすいこと
    • コンポーネントの基盤として、多くのソフトウェアシステムで利用できること

SRP 単一責任の原則

OCP:オープン・クローズドの原則

  • 上位レベルのコンポーネントは下位レベルのコンポーネントが変更されても、変更する必要はない。
  • コンポーネントAがコンポーネントBの変更から保護されるには、コンポーネントBをコンポーネントAに依存させる必要がある。

https://qiita-user-contents.imgix.net/http%3A%2F%2Fwww.plantuml.com%2Fplantuml%2Fpng%2FSoWkIImgAStDuIh9Br0eoLT8oYyfoSzLICaiIaqkoSpFu-9ApaaiBbPm1f6E2jLSjLnScNabgKLfYScf2kw99QdbYPKWOHI5JX0HcBIDRavgMeakr124S8sU7bGzbqDgNWhG6G00?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=5cfb35780649909d9b87fd07ee6f3413

LSP:リスコフの置換原則

  • 抽象型に依存しているコンポーネントは、抽象の派生型に依存しないこと。
    • 依存している抽象の、どの派生型でも動く。

https://qiita-user-contents.imgix.net/http%3A%2F%2Fwww.plantuml.com%2Fplantuml%2Fpng%2FNOqn4i90201xNi47aXTOPAmj_C99YCCC1yx1LUhV7OiRJKfsOLaYIyjU5T8Vc8utp_IAgpIc53S0qXA1Pr4Lk-CJtW1YoMqhn7WHOYF-uh5vvHPkHV_KQFGGuuZTqHXEa_AMqtZquwzvFZPtyfY5XMtjLJy0?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=fcc3bb35df4f90c096398e71e889a5a1

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

DIP:依存関係逆転の法則

  • 「抽象だけに依存しているソースコードが、もっとも柔軟なシステムである」という考え
  • 現実的にはすべてを抽象だけに依存するのは難しいので、「変化しやすい具象」にだけは依存しないように設計する。
    • 変化しやすい具象とは、開発中のモジュールや、頻繁に変更されるモジュールのこと
  • コーディングプラクティスのまとめ
    • 変化しやすい具象クラスを参照しない。代わりにインターフェイスを参照する。
    • 変化しやすい具象クラスを継承しない。
    • 具象関数をオーバーライドしない。
    • 変化しやすい具象を名指しで参照しない。

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

  • どのクラスをどのコンポーネントに含めるか判断するための原則3つ
    • 再利用・リリース等価の原則(REP)
    • 閉鎖性共通の原則(CCP)
    • 全再利用の原則(CRP)
  • REP・CCP(コンポーネントを大きくする)とCRP(コンポーネントを小さくする)は相反している。
    • どれを優先するかはプロジェクトの状況によって考えるべき。
    • 例えばプロジェクト初期ならREP(再利用性)よりもCCP(開発しやすさ)の方が重要視されやすい。

再利用・リリース等価の原則(REP)

  • 再利用の単位とリリースの単位は等価になる。
    • コンポーネントを再利用したくても、リリース番号がなければうまく再利用できない。
    • ひとつのコンポーネントを形成するクラスやモジュールは、同じバージョン・同じリリースプロセス・同じリリースドキュメントを持っていることが合理的である。

閉鎖性共通の原則(CCP)

  • 同じ理由、同じタイミングで変更されるクラスをコンポーネントにまとめること。変更の理由やタイミングが異なるクラスは、別のコンポーネントに分けること。
    • 同じタイミングで変更されることが多いクラスは、ひとつのコンポーネントにまとめておくこと。
    • 変更を1つのコンポーネント内に閉じ込める 閉鎖性(OCPの『クローズド』も同じ意味。)

全再利用の原則(CRP)

  • コンポーネントのユーザーに対して、実際には使わないものへの依存を強要してはいけない。
    • 一緒に用いられることが多いクラスやモジュールは同じコンポーネントにまとめよ、というもの。
    • 密結合していないクラスは同じコンポーネントにまとめない。まとめるクラスはどれも切り離せないものばかりにする。

  • コンポーネントの関連を扱う原則3つ
    • 非循環依存関係の原則(ADP)
    • 安定依存の原則(SDP)
    • 安定度・抽象度等価の原則(SAP)

非循環依存関係の原則(ADP)

  • コンポーネントの依存グラフに循環依存があってはいけない。
  • 依存性逆転の原則を用いて循環依存を解消することができる。

安定依存の原則(SDP)

  • 安定度の高い方向に依存すること。
  • 安定度とは、コンポーネントの「変更しづらさ」のこと。以下のように計算できる。
    • ファン・イン…コンポーネントが依存されている外部コンポーネントの数
    • ファン・アウト…コンポーネントが依存している外部コンポーネントの数
    • I(Instability 不安定さ) = ファン・アウト / (ファン・イン + ファン・アウト)
      • 0  I  1
  • SDPは、コンポーネントのIを依存するコンポーネントのIよりも大きくすべき(不安定にすべき)である。
  • インターフェイスしかないコンポーネントは安定度が高いので、依存するのにちょうどよい。

安定度・抽象度等価の原則(SAP)

  • コンポーネントの抽象度は、その安定度の同程度でなければいけない。
    • つまり、抽象度が高くなる方向に依存すべき。
  • 抽象度は以下のように計算できる。
    • Nc…コンポーネント内のクラス総数。
    • Na…コンポーネント内の抽象クラスとインターフェイス総数。
    • A(抽象度)= Na / Nc
      • 0  A  1
  • 抽象度が低く、安定度が最高のコンポーネント(A=0, I=0)は、柔軟性にかけるので好ましくない。苦痛ゾーンと呼ばれている。
    • 変動性の低いコンポーネントは変更されることがないため、苦痛ゾーンにあっても問題ない。
  • 抽象度が高く、安定度が低いコンポーネント(A=1, I=1)は、抽象化されているのに依存するコンポーネントが存在しない、無駄ゾーンである。
  • コンポーネントにとっての理想的な場所は、抽象度が低く、安定度が低い場所(A=0, I=1)もしくは抽象度が高く、安定度が高い場所(A=1, I=0)である。
    • この2点を結ぶ直線を主系列と呼び、なるべくこの線に乗せるようにする。

第Ⅴ部 アーキテクチャ

  • 優れたアーキテクチャは以下のことをサポートする必要がある。

    • システムのユースケース
      • 振る舞いを明らかにして、システムの意図を明確にすること。
      • 優れたアーキテクチャのショッピングカートシステムは、ショッピングカートに見える
    • システムの運用
      • 顧客の増加に従ってシステムをスケールアップできること。
      • コンポーネント間通信を特定の技術基盤に依らない設計にすれば、簡単にスレッド→プロセス→サービスと移行できる。
    • システムの開発
      • チーム間で干渉しないように、コンポーネントを単独で開発できるようにする。
    • システムのデプロイ
      • 構成スクリプトや設定ファイルの変更に依存せずに「即時デプロイ」を目指す。
  • ビジネスルールとそれ以外の間に境界線を引くことで、ビジネスロジック以外のアーキテクチャ決定を遅延することができる。

    • データベースを決める前に、ビジネスルールの作成とテストに集中することができる。
    • 遅延させた間でよりよいソリューションを検討することができる。
    • 境界線は変更理由が違うコンポーネント間に引く。
      • 例えばGUIの変更とビジネスルールの変更の理由は異なるはず。
      • 単一責任の原則(モジュールを変更する理由は、たった一つであるべき)に従えば境界線を引ける。
    • 具体的には、境界線をインターフェイスとして表現することで実装できる。 interface

レベル

  • 下位レベルのコンポーネントが上位レベルのコンポーネントに依存すべきである。
  • 入出力が近ければ近いほど、下位レベルに位置づけられる。

エンティティ

  • ビジネスマネーを生み出すルールと、ルールに必要なデータをまとめたオブジェクトを「エンティティ」と呼ぶ。
    • クラスに限らず、ビジネスルールとビジネスデータをまとめたモジュールであればよい。

ユースケース

  • アプリケーション固有のビジネスルール(バリデーション等)、エンティティを呼び出す順番を記したオブジェクトを「ユースケース」と呼ぶ。
  • 特定のユーザーインターフェースについては規定せず、渡されるデータ形式を規定する。
    • すなわち、ユースケースの入出力のデータはHttpRequestHttpResponse等にはならない
    • 入出力のデータにエンティティを使わない。「入出力データ形式」と「エンティティ」は変更する理由が異なるため。

クリーンアーキテクチャ

https://qiita-user-contents.imgix.net/https%3A%2F%2Fblog.cleancoder.com%2Funcle-bob%2Fimages%2F2012-08-13-the-clean-architecture%2FCleanArchitecture.jpg?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=5213e15025a41dcc55e4c2d91ca28704

  • クリーンアーキテクチャを採用したシステムの特徴
  • フレームワーク非依存
  • テスト可能
  • UI, データベース, Webサーバ等がなくてもビジネスルールをテストできる。
  • UI非依存
  • データベース非依存
  • 外部エージェント非依存
  • ビジネスルールは外界のインターフェイス(低レベルのコンポーネント)について何も知らない。
  • 依存性のルール
  • ソースコードの依存性は高レベルのコンポーネントにのみ向かってなければいけない。
  • 図で言うところの「外側」から「内側」にのみ依存してよい。
  • 各レイヤの責務
  • エンティティ
  • 外部で何が起きても変更されることがない。
  • ユースケース
  • UIやDBの変更がこのレイヤに影響を及ぼすことはない。
  • アプリケーションの操作の変更のみによって影響される。
  • インターフェイスアダプタ(図のControllerPresenterGatewaysに該当する部分)
  • ユースケース・エンティティ ⇔ DB・Web等の外部エージェント間で、それぞれの利用しやすいデータ構造を変換する層。
  • ユースケース層とインターフェイスアダプタ層間は、単なるデータ構造である「モデル」でデータの受け渡しをする。
  • フレームワークとドライバ
  • フレームワークやデータベースと直接やり取りするコンポーネント。
  • 境界線の越え方
  • 高レベルのコンポーネントから低レベルのコンポーネントへ通信するときは、依存関係逆転の原則を使って境界を超える。
  • 境界線を超えるデータは単なるデータ構造であるべき。エンティティや、データベースの行をそのまま渡すのは依存性のルールに違反する。

Humble Objectパターン

  • あるコンポーネントの振る舞いを「テストしにくい振る舞い」と「テストしやすい振る舞い」に分離するためのデザインパターン。
    • テストしにくいふるまいはHumble Objectとしてなるべくシンプルにする。
  • GUIの振る舞いをPresenterView(Humble Object)に分ける。
    • Presenterはアプリケーションから受け取った値をロジックに沿って整形して、View Model(単なるデータ構造)に詰める。
    • ViewView Modelを見つけて、そのまま表示する。

https://qiita-user-contents.imgix.net/http%3A%2F%2Fwww.plantuml.com%2Fplantuml%2Fpng%2FSoWkIImgAStDuKhEIImkLWWeIYrEpIj9BLAeheKAXMMcbllcfwJce0g1U5m8EHjTNOHcD74GZyiXDIy5Q0y0?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=73c57844313ef17fbb42944bcf77c8fe