Slide 1

Slide 1 text

ほりしょー 現実世界の事象から学ぶ SOLID原則

Slide 2

Slide 2 text

自己紹介 ほりしょー エンジニア@ハコベル株式会社 DDD / Clean Architecture GraphQL / Golang / Ruby 個人開発でカクテルアプリ作成中 🍹 来週リリース予定 @H0R15H0

Slide 3

Slide 3 text

SOLID原則とは? 変更に強く・理解しやすく・再利用できるソフトウェア構造を作るための基本原則 単一責任の原則 (The Single Responsibility Principle / SRP) 開放閉鎖の原則(The Open-Closed Principle / OCP) リスコフの置換原則(The Liskov Substitution Principle / LSP) インターフェース分離の原則(The Interface Segregation Principle / ISP) 依存性逆転の原則 (The Dependency Inversion Principle / DIP)

Slide 4

Slide 4 text

しかし、SOLID原則は難しい 原則が抽象的で理解が困難 (原則=)当たり前過ぎるため、適用するメリットが掴みづらい コード例が現実味を帯びていない OOP初学者の前に立ちはだかる 最初の壁がSOLID原則

Slide 5

Slide 5 text

焼き肉を例にします! 現実世界の事象で具体的に学ぶ 本日のテーマ

Slide 6

Slide 6 text

本スライドの流れ 各原則の概要 1. 原則に違反した例の紹介 2. 原則に従った改善例 3. 各原則のまとめ 4. ※コード例はJavaをベースにしていますが  理解しやすさのために一部文法を書き換えています。ご了承ください。 ※時間の都合上、インターフェース分離の原則は省略します。他4原則が理解すれば容易 梱包 配送 加工 調理

Slide 7

Slide 7 text

単一責任の原則(SRP)

Slide 8

Slide 8 text

単一責任の原則(SRP)とは? “モジュールを変更する理由はたったひとつだけであるべきである” “モジュールはたったひとつのアクターに対して責務を負うべきである” (表現のゆれがあるが意図しているものは変わらない) モジュール ... いくつかのデータや関数をまとめた凝集性のあるもの アクター  ... 変更を望む人(グループ)

Slide 9

Slide 9 text

お肉担当Aさん 主役モジュール ナイフ 単一責任の原則に違反した例:仕込み作業 同じナイフを使用 同じナイフを使用 サラダ担当 新人Bさん

Slide 10

Slide 10 text

お肉担当Aさん 主役モジュール ナイフ 単一責任の原則に違反した例:仕込み作業 同じナイフを使用 同じナイフを使用 複数のアクター(Aさん・Bさん)に 対して責務を負っている状況 →SRP違反 サラダ担当 新人Bさん

Slide 11

Slide 11 text

お肉担当Aさん Aさんが使っていることを知らず ペティナイフに買い替えてしまった 単一責任の原則に違反した例:仕込み作業 サラダ担当 新人Bさん SRPに違反した状態で変更を加えると

Slide 12

Slide 12 text

お肉担当Aさん Aさんが使っていることを知らず ペティナイフに買い替えてしまった 単一責任の原則に違反した例:仕込み作業 お肉の仕込みができない 予期せぬバグ サラダ担当 新人Bさん SRPに違反した状態で変更を加えると

Slide 13

Slide 13 text

お肉担当Aさん Aさんが使っていることを知らず ペティナイフに買い替えてしまった 単一責任の原則に違反した例:仕込み作業 お肉の仕込みができない 予期せぬバグ 意図しないバグを 埋め込む可能性が高まる サラダ担当 新人Bさん SRPに違反した状態で変更を加えると

Slide 14

Slide 14 text

お肉担当Aさん サラダ担当Bさん どうすべきだったのか? SRPに従い、アクターの異なるコードは分割する 各々独立して買い替え可能に

Slide 15

Slide 15 text

お肉担当Aさん サラダ担当Bさん 単一責任の原則違反まとめ 単一責任の原則=“モジュールはたった一つのアクターに対して責務を負うべきである” 違反していると変更により予期せぬバグが発生しうる(=もろい設計) 防止策:アクターの異なるコードは分割する

Slide 16

Slide 16 text

開放閉鎖の原則(OCP)

Slide 17

Slide 17 text

開放閉鎖の原則(OCP)とは? “ソフトウェアの構成要素(クラス、関数、モジュール等)は拡張に対しては開いていて 修正に対して閉じていなければならない” 言い換えると... “ソフトウェアの振る舞いは、既存の成果物を変更せず拡張できるようにすべき” OCPはオブジェクト指向設計の核 『アジャイルソフトウェア開発の奥義 第2版』(SB クリエイティブ)より

Slide 18

Slide 18 text

Aさん 開放閉鎖の原則に違反した例:仕込み作業 Aさんは全ての仕込み作業が頭に入っている状態

Slide 19

Slide 19 text

Aさん 開放閉鎖の原則に違反した例:仕込み作業 今回の主役 Type()による条件分岐に注目 Aさんは全ての仕込み作業が頭に入っている状態

Slide 20

Slide 20 text

Aさん 開放閉鎖の原則に違反した例:仕込み作業 脳内 分岐追加 エビ追加 Aさんは全ての仕込み作業が頭に入っている状態 拡張(エビの追加)に伴って 既存の成果物(Aさん)の脳内も修正 →OCP違反

Slide 21

Slide 21 text

OCP違反 →既存成果物(Aさん)に修正が必要だったこと Aさん以外にも従業員がいる場合、 全従業員にエビの仕込み方法を覚えてもらう(全従業員に対して修正が)必要があり これがOCP違反の問題点 修正に時間がかかる(硬い設計) 修正漏れがあった場合にバグが残る(変更に弱い) 開放閉鎖の原則に違反した場合の問題点 「抽象」を使うことで修正が不要に

Slide 22

Slide 22 text

食材 開放閉鎖の原則違反を改善:抽象化 Aさん

Slide 23

Slide 23 text

肉 Aさん 野菜 エビ 食材 継承 抽象化 Cut() Cut()を実装 Cut()を実装 Cut()を実装 開放閉鎖の原則違反を改善:抽象化 食材の抽象型を用意 食材毎に具象化

Slide 24

Slide 24 text

抽象化によりAの修正は不要に 食材をどれだけ増やそうと“Aクラス”には修正不要 (Aクラスは)修正に閉じて、(具象の食材クラスは)拡張に開いている

Slide 25

Slide 25 text

条件分岐を一つのメソッドに共通化 Aさん (補足)よくある間違った改善 この改善の問題点 共通メソッドがSRPに違反 仕込みを分担したい場合に変更困難 仕込み処理以外に手続きが増えた場合、 条件分岐が乱立する状態は変わらない 食材 Bさん Cさん 共通 条件分岐 仕込み処理 アクターが変わっただけで 本質的には何も改善していない

Slide 26

Slide 26 text

開放閉鎖の原則まとめ 開放閉鎖の原則=“拡張に対しては開いていて修正に対して閉じていなければならない” 違反していると振る舞いの追加に時間がかかる(=硬い設計) OCPによりオブジェクト指向技術から得られる 最大の利益(柔軟性、再利用性、保守性)を享受できる ポイント 抽象をうまく使うことで修正に閉じる 具象クラスの追加により拡張する 種類による条件分岐は疑え 肉 Aさん 野菜 エビ 食材 継承 抽象化 Cut() Cut()を実装 Cut()を実装 Cut()を実装

Slide 27

Slide 27 text

リスコフの置換原則(LSP)

Slide 28

Slide 28 text

リスコフの置換原則(LSP)とは? “派生型はその基本型と置換可能でなければならない。” 言い換えると... “置換可能でないなら派生してはいけない。”

Slide 29

Slide 29 text

餌やり 成長したら出荷 リスコフの置換原則に違反した例:牛舎 とある牛舎のルーティーン

Slide 30

Slide 30 text

リスコフの置換原則に違反した例:牛舎 コードに落とす 餌やり 成長したら出荷 餌やりを繰り返し 体重が一定超えたら 出荷

Slide 31

Slide 31 text

リスコフの置換原則に違反した例:牛舎 新たに乳牛を飼育する 餌やり 乳搾り 乳牛と肉牛は置換可能ではない(LSP違反)が 派生クラスを作成すると・・・ 出荷

Slide 32

Slide 32 text

リスコフの置換原則に違反した例:牛舎 すでに存在していたCowを継承し乳牛クラス(MilkCow)を作成

Slide 33

Slide 33 text

リスコフの置換原則に違反した例:牛舎 Farmクラスのルーティーン作業は、

Slide 34

Slide 34 text

リスコフの置換原則に違反した例:牛舎 Farmクラスのルーティーン作業は、 LSPに違反すると必然的にOCPに違反する 具象クラスを参照せざるを得ない OCP違反

Slide 35

Slide 35 text

餌やり 成長したら出荷 肉牛農場 リスコフの置換原則に違反しないためには SRPに従ってそもそも農場を分ける 乳牛農場 餌やり 乳搾り 派生するのではなく共通部分を括り出す

Slide 36

Slide 36 text

リスコフの置換原則まとめ リスコフの置換原則=“派生型はその基本型と置換可能でなければならない” 違反していると自動的にOCPに違反し硬い設計に IS NOT A 肉牛の品種・個体レベルにおいては置換可能かも 立場(ドメイン)によって何が置換可能なのか変わりうる リスコフの置換原則違反の見つけ方: 基本クラスから何らかの機能を省いてしまっているような派生クラスは怪しい

Slide 37

Slide 37 text

(補足)置換可能な条件 置換可能な条件=派生して良い条件 1〜4を満たしていること 事前条件(preconditions)を、派生型で強めることはできない。派生型では同じか 弱められる。 1. 事後条件(postconditions)を、派生型で弱めることはできない。派生型では同じ か強められる。 2. 不変条件(invaritants)は、派生型でも保護されねばならない。派生型でそのまま 維持される。 3. 基底型の例外(exception)から派生した例外を除いては、派生型で独自の例外を投 げてはならない。 4. wikipedia: リスコフの置換原則より

Slide 38

Slide 38 text

依存性逆転の原則(DIP)

Slide 39

Slide 39 text

依存性逆転の原則(DIP) 上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュール も「抽象」に依存すべきである。 1. 「抽象」は実装の詳細に依存してはならない。実装の詳細が「抽象」に依存すべき である。 2. アジャイルソフトウェア開発の奥義 第2版より 言い換えると、 “モジュールの依存関係が具象(下位)ではなく抽象(上位)だけを参照すべき”

Slide 40

Slide 40 text

荷物を積む 車両A 荷主 運送会社A 上位 下位 依存性逆転の原則に違反した例:配送 下位モジュールに依存 (DIP違反)

Slide 41

Slide 41 text

荷物を積む 荷主 運送会社A 車両B 車両Aが 故障 修正 車両B に変えて 車両が故障すると 依存性逆転の原則に違反した例:配送 上位 下位

Slide 42

Slide 42 text

荷物を積む 荷主 運送会社A 車両B 車両B に変えて 車両が故障すると 依存性逆転の原則に違反した例:配送 上位 下位 車両Aが 故障 下位における変更の影響が上位伝達しやすい 修正

Slide 43

Slide 43 text

下位モジュールへの依存による問題点 変更にもろい もし車両Aへの依存箇所が数百もあったらどうだろうか? 車両Bへの変更完了に時間が必要 変更漏れがあった場合、バグが残る 運送会社がそもそも倒産してしまった場合どうするのか? 車両と同様 荷主の業務(上位)が車両故障等の外部環境(下位)の変更に強くなるためには? 荷主が振る舞いを定義して、それを満たす車両を各社で定義してもらう。 →依存性逆転の原則

Slide 44

Slide 44 text

車両A 荷主 運送会社A 荷物を積む 抽象車両 車両B どれでも良い 依存性逆転の考え方 上位モジュールのあるべき姿 期待してくれる振る舞いをしてくれれば 下位の詳細はなんでも良い

Slide 45

Slide 45 text

運送会社A 車両A 荷主 依存 step1)車両故障への対応:抽象に依存

Slide 46

Slide 46 text

運送会社A 車両 荷主 依存 車両A 車両B 依存 step1)車両故障への対応:抽象に依存 具象車両への依存は解消

Slide 47

Slide 47 text

運送会社A 車両 荷主 依存 車両A 車両B 依存 step2)運送会社への依存を断ち切る:逆転 未だ特定の運送会社に依存

Slide 48

Slide 48 text

step2)運送会社への依存を断ち切る:逆転 あるべき関係性 運送会社A 車両 荷主 依存 車両A 車両B 依存 荷主パッケージ 特定の運送会社への依存も解消

Slide 49

Slide 49 text

処理の流れに注目 運送会社A 車両 荷主 依存 車両A 車両B 依存 荷主パッケージ 処理の流れ 依存の向きと 処理の流れが逆転 依存性逆転

Slide 50

Slide 50 text

依存性逆転の原則まとめ 依存性逆転の原則 =“モジュールの依存関係が具象(下位)ではなく抽象(上位)だけを参照すべき” 違反していると変更にもろい設計に ポイント 抽象化 1. 抽象を上位モジュールに移動 2. 車両A 荷主 運送会社A 荷物を積む 抽象車両 車両B どれでも良い

Slide 51

Slide 51 text

全体まとめ

Slide 52

Slide 52 text

単一責任の原則 (The Single Responsibility Principle / SRP) アクターごとにモジュールを用意する(高凝集)ことで変更しやすく 開放閉鎖の原則(The Open-Closed Principle / OCP) (OOPの本質)抽象化により変更に強く リスコフの置換原則(The Liskov Substitution Principle / LSP) 無理な派生(継承)を行わずOCPに準拠する 依存性逆転の原則 (The Dependency Inversion Principle / DIP) 下位モジュールへの依存を断ち切り変更に強く

Slide 53

Slide 53 text

参考文献 アジャイルソフトウェア開発の奥義 第2版 著:ロバート・C・マーチン / 訳:瀬谷 啓介 Clean Architecture 達人に学ぶソフトウェアの構造と設計 著:ロバート・C・マーチン / 訳:角 征典・高木 正弘

Slide 54

Slide 54 text

ご清聴ありがとうございました! ほりしょー OOC引き続き楽しみましょう!!!