Clean Architecture 読書メモ

5章 OOP
- OOPのアーキテクトから見た本質は、依存関係の逆転である。
- プログラム内部の呼び出しツリーでは、main関数が上位レベルの関数を呼び出し、それらが下位レベルの関数を呼び出す。つまり、mainが上位レベルの関数に依存する。
- ポリモーフィズムを使えば、インターフェイスを用意することで、これらの呼び出しの依存関係を逆転できる。
OO言語が便利で安全なポリモーフィズムを提供しているというのは、ソースコードの依存関係は(たとえどこであっても)逆転できることを意味する。
OO言語…は、システムにあるすべてのソースコードの依存関係の方向を絶対的に制御できる。…これはパワーだ!…これこそが本当のOOである。少なくともアーキテクトの観点からはそう言える。
-
ポリモーフィズムのような考え方は、stdinやstdoutのような抽象化されたデバイスをCから使うときに既に実装されている。
-
それ以外にも、関数へのポインタを利用することでポリモーフィズムは実現できた。
-
ただし、関数へのポインタは危険である。プログラマーが規約を覚えておかなければならない。
-
OOはこのような危険性を排除するために、規律を課すものと考えられる。
-
依存関係を制御することで、独立してデプロイ可能なコンポーネントを制御できる。

6章 関数型プログラミング
- 関数型言語の変数は変化しない。
- 競合状態や更新異常の問題はすべて可変変数にある。変数が不変であれば、この問題は解決される。
- ストレージとプロセッサーが無限にあれば、不変性は使える。実際には、ある程度の妥協をすることになる。
- 最も一般的な妥協は、可変コンポーネントと不変コンポーネントに分離することである。

7章 単一責任の原則
変更を望む人たちをひとまとめにしたグループをアクターと呼ぶことにする。
単一責任の原則は次のようになる:
- モジュールはたった一つのアクターにして責務を負うべきである
- この場合のモジュールは、いくつかの関数やデータをまとめた凝集性のあるもの。
一つの関数は一つのことだけを行うべき、という法則は存在するが、SOLIDの単一責任の原則とは異なる。単一責任の原則は、もっと上位レベルの概念である。
- この場合のモジュールは、いくつかの関数やデータをまとめた凝集性のあるもの。
Employee
クラスがcalculatePay()
、reportHours()
、save()
メソッドを持っているとする。
これらは、それぞれ報告先が異なるので、別のアクターに対する責務を負っているので、単一責任の原則に違反している。
これを解決するには、
- Employeeのデータを関数から切り離す
- それぞれのアクターへの報告を行うモジュールは、EmployeeDataに依存する別々のモジュールとして切り出す
- この場合は、
PayCalculator
,HourReporter
,EmployeeSaver
の3つのモジュールに切り出す。
3つのインスタンスを追跡しなければいけない辛さを解決するために使われるのがFacadeパターンである。
- GoFデザインパターンに登場する。
-
PayCalculator
,HourReporter
,EmployeeSaver
の3つへの参照を持ち、内部処理を隠蔽する。- 各モジュールにはメソッドが一つだけ、ということはない。給与計算や帳票出力やデータの保存に必要なprivateメソッドは多くなる。
- 従業員に関する情報を他のモジュールが取得したい場合は、Facadeクラスを参照させる。
- 各モジュールがどのようなprivateメソッドを持っているかはFacadeクラスは関与しない。
- データはFacadeクラスに持たせてもいいかもしれない。
所感
わざわざ3階層にまですべき理由がまだピンときていない。一人で作っていたらメソッド同士の結合も頭に入っているし、そこまで混乱しない気がする。複数人で同時作業する場合に影響が波及するのが嫌なんだろうな、というのは理解するもののあまりリアリティはない。
多分この前提がとても重要
- 各モジュールにはメソッドが一つだけ、ということはない。給与計算や帳票出力やデータの保存に必要なprivateメソッドは多くなる。
メソッド同士の結合がマネージできなくなるぐらいになってきたら分割したほうがいい、というのは完全に同意。

8章 オープン・クローズドの原則
Bertrand Meyerが1988年に以下の原則を提唱した
ソフトウェアの構成要素は拡張に対しては開いていて、修正に対しては閉じていなければならない。
別の言い方をすると以下:
- ソフトウェアコンポーネントは、既存の成果物を変更せずに拡張できなければならない。
- コンポーネントAがコンポーネントBの変更から保護されるべきならば、コンポーネントBからコンポーネントAへ依存すべきである。
- もっとも変更を保護されるべきなコンポーネントは、ビジネスルールを含んでいる
- 依存関係を制御するために、インターフェースを導入する
何を言っているかは以下の記事で完全に理解。
-
SOLID原則 ◆オープン・クローズドの原則◆
- switch文で会員種別ごとに処理を分けているのは、リスコフの置換原則にも反している気がする。
- まあそもそもBad codeの方はスーパータイプと実装を分割できていないんだけど

9章 リスコフの置換原則
Barbara Liskovが1988年に派生系について以下のように述べた
S型のオブジェクトo1の各々に、対応するT型のオブジェクトo2が1つ存在し、Tを使って定義されたプログラムPに対してo2の代わりにo1を使ってもPの振る舞いが変わらない場合、SはTの派生系であるといえる。
スーパタイプとサブタイプがあり、ある別のモジュールがそのスーパタイプに依存している場合、モジュールの中にサブタイプを意識した構造(サブタイプの種類でswitch文を書くとか)を書くのをやめろ、だと理解している。(その場合、スーパタイプとサブタイプ間のモデリングができていない場合が多い)
- 依存性逆転の原則との区別は、自分の理解では以下:
- 依存性逆転の原則は、具象ではなくスーパータイプに依存すべきという原則と理解。
- Liskovの置換原則は、使う側を意識していなくて、スーパータイプとサブタイプの関係が完全に置き換えれられるどうかを意識している。
- 実際、Liskovの置換原則に違反すると、使う側のモジュールでサブタイプを意識して条件分岐しないといけなくなり、実質的に具象に依存することになり、依存性逆転の原則にも違反することになるので、観点の違いだけな気がする。
- スーパータイプとサブタイプの関係が正しく分かれているとしても、明示的に具象に依存させることはできるので、まあ別の原則としていい、というのは理解。
長方形・正方形問題
数学的には、正方形は長方形に含まれるが、モデリングの際に
- 長方形に
setH
とsetW
を持たせる - 正方形は長方形を継承する
とするのはLiskovの置換原則違反である。
なぜなら、正方形はHとWを同時に変えなければいけないから。
つまり、
- 派生型は事前条件を強めることはできない。
- 派生型は事後条件を弱めることはできない。
- 派生型が出す例外は上位の型が発生する例外の派生型か、同じ例外でなければならない。

11章 インターフェース分離の原則
使わないメソッドがあるモジュールに依存すべきではない、という理解
- 使いたいメソッドm1のためにTに依存するが、m2メソッドは使わない。ただし他のモジュールがm2メソッドを使っている。
- こういう場合は、Tの責務が微妙に異なっている可能性が高い。
- Tの変更により、他のモジュールが壊れるのを防ぐため、使うメソッドだけを分離したインターフェースをクライアントごとに用意すべき。
- Tはしばしば変更されるが、インターフェースはあまり変わらないので、あるクライアント向けの変更が別のクライアント向け機能を壊す可能性を小さくできる。
言語との関係
静的型付け言語ならば、Tが変更された場合に依存しているコンポーネントの再コンパイルが必要となる。(ので、インターフェース分離の原則違反は明らかに有害である。)
動的型付け言語ならば、依存性はソースコード内には存在せず、実行時に推論される。
動的型付けならインターフェイス分離の原則に違反してもよいわけではない(前述の通り、モジュールの変更による破壊の連鎖の問題がある)。

12章 依存性逆転の原則
具象ではなく、抽象に依存するべきである。
ただし、明らかに現実のソフトウェアシステムは数多くの具象に依存している。
- Stringクラス
- OS
- DBMS
重要な観点は、これらの具象コンポーネントが他のコンポーネントと比べて安定しているかどうかである。
上記の具象は、その内容がコロコロ変わることがなく、通常は安定しているとみなして良い。
従って、より本質的には、変化しやすいものではなく、変化しにくいものに依存すべきという原則と言って良い。
Abstract Factory パターン
大体以下の図で理解したかな、という感じだけど
派生型が何種類もある時は派生型の種類ごとにインターフェースを作ってそれに依存させるんだっけ?
違うよね?
依存させるインターフェースは派生型のスーパタイプ一つだけ、なきが…
- しっくりきていないのでデザインパターンもちゃんと勉強しないとな