🙄

クリーンアーキテクチャー本まとめ

2022/02/20に公開

Ⅰ部

1章

  • 「アーキテクチャ」と「設計」の言葉の使い分け、P33
  • ソフトウェアアーキテクチャの目的は求められるシステムを構築・保守するために必要な人材を最小限に抑えること、P34
    • 「人材」というよりも「コストを削減する」という意味で合ってるか?
  • 後でクリーンにすればいいという姿勢で市場に出すことを優先しても、結局後でクリーンにすることはない。
    短期的にも長期的にもクリーンなコードを書くよりも崩壊したコードを書くほうが常に遅い。P39
    • 短期的には早くなると思ってた。けどこのページのデータによると短期的にも遅くなるらしい。

2章

  • ソフトウェアの価値は「振る舞い」と「構造」である。P41
    • プログラマはマシンがステークホルダーのためにお金を生み出したり、節約したりするようにマシンに振る舞いを与える
    • 変更の難易度は変更の形状ではなく、変更のスコープに比例しなければならない。
      形状に合わせると不釣り合いなほどコストがかかる
      アーキテクチャが特定の構造を選択していると新しい機能がその構造に適さない可能性が高くなる
      • 提供する価値の「構造」とは、変更するときに変更内容(スコープ)に適したコストの大きさであるべきと言う意味だと僕は考えた
  • ソフトウェア開発チームには、機能の緊急性よりもアーキテクチャの重要性を強く主張する責任が求められる、P44
    • 所属部署の都合上、企画さんと関わることはないけど、企画と関わるときにこのスタンスは必要
  • 柔軟性と拡張性の意味の違いを確認したいんですけど、柔軟性は交換しやすい、拡張性は新しい機能を追加できるといった意味であってますか?、P43と45

Ⅱ部

3章

  • オブジェクト思考プログラミング、P50
    • 関数呼び出しのスタックフレームをヒープに移動できること。
      それによって関数から戻ってきた後でも関数で宣言したローカル変数が存在し続けられる
      この関数はクラスのコンストラクタになり、ローカル変数はインスタンス変数に、ネストした関数はメソッドになった
      その後、規律のある関数ポインタの仕様によって必然的にポリモーフィズムの発見につながった
    • オブジェクト指向プログラミングは間接的な制御の移行に規律を課すもの
  • 関数型プログラミング
    • 代入に規律を課すもの

4章

  • 構造化プログラミングについてほぼ理解できてない
  • 構造化言語がアーキテクチャの話とどう繋がるのか分からない

5章

  • OOを提供すると主張している言語は、C言語で完璧に実現できていたカプセル化を弱体化させてしまっている、P62
  • 依存関係逆転とは、依存関係(継承関係)が制御の流れと逆転すること、P48
    ポリモーフィズムは、ソースコードの依存関係がどこでも逆転できるということを意味する
  • ソースコードにあるすべての依存関係はインタフェースを挿入することで逆転可能になる
    ソースコードの依存関係を絶対的に制御することができる
  • 「結果としてソースコードと同じ依存関係のまま」ってどういう意味?、P69
    依存関係は逆転してるんじゃないの?制御関係が同じのままってこと?
  • ソフトウェアアーキテクトにとって、OOとは「ポリモーフィズムを使用することでシステムにあるすべてのソースコードの依存関係を絶対的に制御する能力」、P70
    • アーキテクトにおいてはポリモーフィズムが一番重要らしい

6章

  • 関数型言語の変数は変化しない
    • メリットは、競合状態、デッドロック状態、並行更新の問題の原因がすべて可変変数にあってそこを考慮できるから
      並行処理のアプリケーションにおいて直面するあらゆる問題は、可変変数がなければ発生しない
      それじゃあ関数型言語の並行処理を書くのはそう難しくない?
    • トランザクショナルメモリとは?、P73
    • イベントソーシングとは?、P75
    • 関数型プログラミングがアーキテクチャの話とどう繋がるのか分からない

Ⅲ部

  • SOLID原則の目的は以下のような性質を持つ中間レベルのソフトウェア構造を作ること
    • 変更に強いこと
    • 理解しやすいこと
    • コンポーネントの基盤として多くのソフトウェアシステムで利用できること

7章(単一責任の原則)

  • 「モジュールを変更する理由はたったひとつだけであるべき」ではなく、「モジュールはたったひとつのアクターに対して責務を負うべきである」、P82
    • ずっと前者だと思ってた。設計してるときにアクターを意識してるかというとできてない。みなさんはクラス設計をするときにどうしてますか?
  • 所定労働時間の例が少しイメージしにくい。所定労働時間の計算方法がアクターによって異なるというのがイメージしづらい、P83
  • アクターごとに分けたとき、3つのクラスをインスタンス化して追跡しなければならない。これを解決するために一般的に使われるのがFacadeパターン、P85
  • 単一責任の原則は関数やクラスに関する原則だが、コンポーネントレベルでは閉鎖性共有の原則(OCP)、
    アーキテクチャレベルではアーキテクチャの境界を作るための「境界の軸」と呼ばれる。

8章(オープンクローズドの原則)

  • 拡張に対しては開いていて修正に対しては閉じていなければならない。既存の成果物を変更せず拡張できるようにならなければならない。、P87
  • OCPはクラスやモジュールを設計する際の指針となる原則だと考えてる。けどコンポーネントのレベルを考えたときに、この原則はさらに重要なものとなる。、P87
    • コンポーネントのレベルとは?、P91に書いてるけどこれがOCPである理由がよくわからない
  • 使用の関係って、A→Bに依存関係があるってことでいい?、P89
  • コンポーネントAがコンポーネントBから変更されるべきなら、コンポーネントBからコンポーネントAへ依存するべき
  • P91のアーキテクチャレベルにおけるオープンクローズドの原則で、クローズドはわかるけどオープンはどういう点でオープンになってる?、P91
  • インタフェースの役割でもう一つ別のがあり、推移的にInteractorの内部を知りすぎないように保護すること
    推移的な依存関係は、「ソフトウェアのエンティティは自分が直接使ってないものに依存すべきでない」という原則に反している。、P91
  • OCPの目的は、変更の影響を受けずにシステムを拡張しやすくなることだ。目的を達成するためにシステムをコンポーネントに分割して
    コンポーネントの依存関係を階層構造にする。そして上位レベルのコンポーネントが下位レベルのコンポーネントの変更の影響を受けないようにする。

9章(LSP)

  • リスコフの置換原則って結局継承の考え方と何が違う?
    LSPは継承の使い方の指針になるものだと考えられていたが今ではインターフェイスと実装に関する設計の原則になっている
    例えば、Java風のインタフェース、Rubyであれば同じメソッドシグネチャ、webであれば同じRESTインタフェースに応答するサービス、P95
    LSPに違反したときにどうなるか、タクシー会社の例がイメージしやすい、P96
  • LSPに違反するとシステムのアーキテクチャが特別な仕組みだらけになってしまう、P97

10章(ISP)

  • 再コンパイルと再デプロイが必要になるって書いてるの意味わからなかったけど、静的型付け言語のことか。動的型付けでは考えなくていい、P100

11章(DIP)

  • ソースコードの依存関係が抽象だけを参照しているようなもの。それが最も柔軟なシステムである。これが依存関係逆転の原則の肝。
    SOLID原則でOとDは、依存性を逆転させなさいって言ってて結局同じこと言ってるんじゃないの?、P103
  • 「具象関数をオーバーライドしない」はなんで?、P104
  • JavaではAbstract Factoryパターンを使ってオブジェクトの具象定義を含むソースコードへの依存を避ける
    要は生成する処理を別クラスに持たせるってことかな?、P105
  • 依存関係逆転の法則を満たしていないがこれはよくあること。完全に取り除くことはできないが、具象コンポーネントを少数に絞り込みそれらを他のシステムの他の部分と分離することはできる
    依存関係逆転の法則をすべて満たす必要はないんだね、というかできない、P106

Ⅳ章

  • コンポーネントの原則は部屋を組み合わせて、建物を作る方法を伝える原則

12章

  • コンポーネントとはデプロイの単位のこと。これは一般的な定義?、P109
    homes-pc、NCAppって単位でいいか?
    コンパイル型言語ならバイナリファイル、インタプリタ言語ならソースファイルになる。
  • コンパイル、メモリとかの話。
    メモリにおいて、プログラムとライブラリの断片化が起きてた。その解決策がリローケータブルなバイナリだった。
    コンパイラが出力するバイナリコードに手を加えて、スマートローダでメモリに再配置できるようにした。スマートローダって何?、P112
  • リンクローダのおかげでプログラムを個別にコンパイルやロードができるセグメントに分割できるようになった。
  • プログラマと効率化の戦いの歴史について。マーフィーの法則とムーアの法則、P114
  • 動的にリンクされたファイルを実行時にプラグインできると書いてるけど、ライブラリとかのことでOK?、P114
    コンポーネントの設計方針の前に、コンポーネント単位でプログラムを高速に実行できることの歴史を説明してたのがこの12章

13章

  • 再利用・リリース等価の原則(REP)
    再利用の単位とリリースの単位は等価になる。
    コンポーネントは凝集性のあるクラスでなければならない。アクターが1つである必要はある?、P118
    また、まとめてリリース可能でなければならない
  • 閉鎖性共通の原則(OCP)
    コンポーネントを変更する理由が複数あるべきではない。単一責任の法則と近い、P119
    オープンクローズドの原則とも近い。オープンの具体例が思いつかない。
  • 全再利用の原則
    実際には使わないものへの依存を強要してはいけない
    ISPを一般化したもの
    homes-pcはNCApp内のクラスの全部を使ってるわけではないよね?
  • コンポーネントの設計はこの3つの要素をいかにバランス良くやるか
    優れたアーキテクトは現在の懸念事項に見合った落とし所を見つける

14章

  • この14章で扱う内容もコンポーネントに関する原則です。
  • 非循環依存関係の原則(ADP)
    • 意味
      • コンポーネントの依存グラグに循環依存があってはならない
    • 問題
      • 現場でよく起こる問題が、昨日動いてたプログラムが今日動いてない。「二日酔い症候群」というらしい。
    • ソリューションが2つできた
      • 1,週次ビルド、P126
        • しかしこの手法にもデメリットがあり。プロジェクトが成長すればするほど週1ビルドだと間に合わなくなる。
          また、ビルド周期を伸ばせば伸ばすほど統合に時間がかかるのでPJの失敗するリスクが高くなる
      • 2,循環依存の除去、P126
        • 今回重要なのはこの手法。
        • 開発環境をリリース可能なコンポーネントに分割すればいい。しかし、コンポーネントの依存構造をきちんと管理しておくことが必要条件である。
          そして循環依存であってはならない。コンポーネント図を見たときに矢印をたどってもとのコンポーネントに戻ることができなければ循環依存がないと言える。
        • リリースの影響を受けるコンポーネントを調べるのは簡単で、図の矢印を逆向きにたどればよく。
          コンポーネントの依存関係を把握できているからこそ、ビルドとリリースが明確である。
        • もし循環依存があると、依存してるコンポーネントたちを統合してビルドする必要がありテストもリリースも面倒になる。また、ビルドの順番を決めるのも難しい。
        • もし循環依存になった場合は、2つの方法でコンポーネントの循環依存を排除することが可能。
          • 依存関係逆転の法則を適用する
          • 元々存在する依存してるコンポーネントと依存されてるコンポーネントとは別で新しいコンポーネントを作り、
            両方のコンポーネントが依存するクラスをこの新しいコンポーネントに作る
    • トップダウンの設計
      • ここはコンポーネントのお話。
      • コンポーネントの構造はシステム設計の際に検討するだけのものではなく、その後もどんどん変わる。
      • コンポーネント図はアプリケーションのビルド可能性や保守性を見るための地図のようなもの。
      • 依存構造の中で最優先に対処すべき問題が変動性の分離である。
        コンポーネント図を書くことで頻繁に変更されるコンポーネントが安定したコンポーネントに影響を与えてる/与えてないことを確認する。
  • 安定依存の法則(SDP)
    • 意味
      • 安定度の高い方向にすること
        変動を想定したコンポーネントは変更しづらいコンポーネントから依存されてはいけないうこと
    • 安定度とは
      • 安定度とは何かを変更する際に要する労力と関連してる
        ソフトウェアを変更しづらくするのは、多数のソフトウェアコンポーネントから依存されるようにする。
        コンポーネントがコンポーネントに依存ざされてるとき、それらのコンポーネントに対する責務を負ってるという
        他のコンポーネントに依存してないものを独立コンポーネントという
        他のコンポーエントに依存してるものを従属コンポーネントという
    • 安定度の指標
      • I=ファンアウト/(ファンアウト+ファンイン)で表す
        ファンインは依存入力数、ファンアウトは依存出力数。I=0が最も安定してて、I=1が最も不安定なコンポーネントを表す
      • コンポーネントの依存性の方向を順番にたどるとIの値が減少していくべきということ。
    • 問題と解決方法
      • 依存方向とは逆に安定度がならない場合は、依存関係逆転の法則を使う
      • インタフェースが含まれる抽象コンポーネントは安定度が非常に高いので、
        安定度の高いコンポーネントが依存する対象としては理想的。
      • 動的型付け言語には抽象コンポーネントは存在しない。
        Rubyでは依存性逆転はどう実装すればいい?、moduleがその代わり?、P137
  • 安定度・抽象度等価の原則(SAP)
    • 意味
      • 抽象度はその安定度と同程度でなければならない。
    • 上位レベルの方針
      • 上位レベルの方針をカプセル化したものは、安定度の高いコンポーネントに配置しなければならない
      • しかし、上位レベルの方針を安定度の高いコンポーネントに配置すると、方針を表現してるソースコードが表現しづらくなる。
        そして、そのアーキテクチャは柔軟性に欠けたものになる。
      • 安定度が高いコンポーネントを変更するにはどうすればいいか?オープンクローズドの原則を考えれば良い。つまり、抽象クラスで実装すればいい
        抽象クラスとインタフェースの違いって何?、P138
    • SAPとは
      • 安定度の高いコンポーネントは抽象度も高くあるべき
        なのでインタフェースと抽象クラスで構成するべき。安定度が高くて、抽象度が高く拡張可能なコンポーネントは柔軟である。
        安定依存の法則(SDP)と安定度・抽象度等価の原則(SAP)の組み合わせが、コンポーネント版の依存関係逆転に相当する。
        つまり、抽象度が高くなる方向に依存するべきということ。
    • 抽象度の指標
      • A=コンポーネント内の抽象クラスとインタフェースの総数/コンポーネント内のクラスの総数
      • IとAをグラグの縦軸横軸においたときに、コンポーネントが無駄ゾーン苦痛ゾーンに属さないようにする
        P140のグラフ参照
      • 苦痛ゾーン
        安定度が最高の具象コンポーネントである。柔軟性に欠けているので好ましくない。
        抽象度が低いために拡張ができないし、安定度が高いため変更もしづらい。例えば、データベーススキーマがありますと。
      • 無駄ゾーン
        抽象化されているが依存するのがないものなので、考える必要なし
      • 主系列
        苦痛ゾーン、無駄ゾーンを避けて考えると主系列周辺が妥当になるから。
        主系列からの距離を測る指標としてD=|A+I-1|とおく。この指標をもとにコンポーネントを評価できる
        これまででてきた指標を業務で使ったことがある人はいるのか?、

15章アーキテクチャとは?

  • アーキテクチャの形状の目的はそこに含まれるソフトウェアシステムの開発、デプロイ、運用、保守を容易にすることである。
    • それらを容易にするための戦略は、できるだけ長い期間、できるだけ多く選択肢を残すことである。
    • アーキテクチャの目的は「システムを適切に動作させること」ではない
  • 開発
    • システムのアーキテクチャによって開発チームが開発しやすくなるようなシステムにすべき。
    • チームの構成が異なればアーキテクチャの決定も異なる。
  • デプロイ
    • システムを単一のアクションで簡単にデプロイできるようにすること
    • V5はデプロイの方針どうなってる?
  • 運用
    • ソフトウェアアーキテクチャを変更しなくてもハードウェアを追加すれば解決できる
    • システムの運用ニーズを伝えるという役割はある
  • 保守
    • アーキテクチャを慎重に考え抜けば保守コストは大幅に低下する。
  • 選択肢を残しておく
    • 振る舞いの価値と構造の価値がある
    • 多く選択肢を残すのは「重要ではない詳細」である
    • ソフトウェアシステムは大きく2つの要素に分割できる「方針」と「詳細」だ。
    • 詳細は方針の振る舞いに影響を与えるものではない
    • 詳細はIOデバイス、データベース、ウェブシステムなどが含まれる
    • アーキテクチャの目的は方針とは無関係に詳細を決めながら方針をシステムの最も重要な要素と認識する
      システムの形状を作ること。そして詳細の決定を延期や留保することができる。決定を遅延できれば適切に作るための情報が手に入る。

16章独立性

  • ユースケース
    • 15章の保守と同じ文脈で言ってる?
    • アーキテクチャがシステムの意図をサポートしなければならない。
  • 運用
    • 求められるユースケースに対してスループットと応答時間をサポートしなければならない
    • スループットと応答時間をサポートしなければならない
  • 開発
    • コンウェイの法則
  • デプロイ
    • 目指すのは即時デプロイ。
  • レイヤーの切り離し
    • ユーザーインタフェースを変更する理由は、ビジネスルールを変更する理由とは関係がない。
    • アプリケーションと密接に結びついてるロジックと、ビジネスルールも異なる理由で変更される。
    • データベース、スキーマなどもUIとは関係のない技術的詳細である
    • システムは切り離された水平レイヤーで分割される
  • ユースケースの切り離し
    • システムを水平に分割するときはそれらを薄く垂直にユースケースとしても分割する
      • UI、 ビジネスルール、データベースをユースケースごとに分ける
      • 新しいユースケースを追加するときに古いユースケースに影響を及ぼすことがない
  • 切り離し
    • ユースケースの切り離しは運用にも適用可能
      • いまいちわからん
  • 独立した開発が可能
    • チーム同士の干渉が緩和される
  • 独立デプロイ可能性
    • レイヤーやデプロイの柔軟性も高まる
  • 重複
    • 重複してるように見えてもユースケースが異なる場合はDRYにしない
      • こういう類のものを本物の重複ではないという
  • 切り離し方式
    • レイヤーやユースケースを切り離す方法はいくつかある。どれくらいの粒度で切り離すかという話の用に見える。P164
      • ソースレベル
      • デプロイレベル
      • サービスレベル
    • プロジェクトの初期段階では最適な方式は難しい
    • 優れたアーキテクチャであればシステムはモノリシックとして生まれ、単一ファイルでデプロイされ、
      独立してデプロイ可能なな単位になるまで成長し、最後はサービスやマイクロサービスまでたどり着くことが可能である。
      その後、物事が変わったときにはこれまでの流れを逆転させ、モノリシックまで戻せるようになっておくべき。
      • モノリシックまで戻したほうがいいということ?それはなぜ?、P164

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

  • 導入
    • ソフトウェアアーキテクチャとは境界線を引く技芸である。これをバウンダリーと呼んでる。
    • 初期に境界線を引くのは決定をできるだけ遅らせるため
      • 一番大きな問題になるのは早すぎる決定との結合
      • ビジネス要件とは関係のない決定である。フレームワーク、データベース、ウェブサーバー、ユーティリティライブラリ、DIなど。
  • 結合の悲しい物語
    • 具体例がイメージしづらい
    • 方針→詳細に依存関係であってもモック化スタブ化すればテスト普通に対応できるくないか?
    • SOA(サービス思考アーキテクチャとは?)
  • FitNesse
    • データアクセサとデータリポジトリの間にインターフェイスを置く
      • ビジネスルールとデータベースの間に境界線を引くことで、ビジネスルールはデータベースについて知ることはなかった
      • テストを遅くするデータベースがないのでテストを高速に実行することができる
    • 境界線を引けば決定を延期保留できる
  • 境界線は何か?
    • 重要なものと重要なものでない間に引く
    • ビジネスルールが知る必要があるのはデータを取得・保存する機能が存在するということだけ
  • 入力と出力
    • IOは無関係
  • プラグインアーキテクチャ
    • プラグイン構造を前提にして着手しておけば変更しやすい
    • 境界線は変更の軸があるところに引く、境界線を挟んだコンポーネントはそれぞれ変更の頻度や理由が違っている
      • 単一責任の原則がどこに境界線を引けばいいか教えてくれる
  • まとめ
    • DIPとSAPを適用すると境界線を引ける

18章 境界線の解剖学

  • 恐怖のモノリス
    • 依存性逆転をしないとDataが境界の呼び出される側にある
    • 依存性逆転を使うと実行時の依存性をコンパイル時の依存性の反対にする
      • データ構造の定義が境界の呼び出し側にあるのはなぜ?
    • モノリスに含まれるコンポーネント間の通信は、非常に高速で安価である。通信はにぎやかなものになる。
  • デプロイコンポーネント
    • RubyのGemなど
    • デプロイにコンパイルは不要なので、これらのコンポーネントはバイナリやデプロイ可能な形式でデプロイされる
    • モノリスと同様に単なる関数呼び出しなので安価になる。最初に動的リンクや実行時読み込みが必要になる。
  • ローカルプロセス
    • ソケットかメールボックスやメッセージキューなどのOSが提供している通信機能を使って行われる
  • サービス
    • 最も強い境界はサービス。あらゆる通信はネットワーク経由で行われることが前提。
    • 関数呼び出しと比べると遅く、レイテンシーを考えないといけない
  • まとめ
    • 境界にはローカルでにぎやかな境界とレイテンシーに影響される境界が混在している。

Discussion