Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
2025/05/28 社内勉強会 SOLID原則ではじめる よりよい設計の第一歩 1
Slide 2
Slide 2 text
2 ● オブジェクト指向でよりよい設計を目指す ● 基本方針としてSOLID原則の解説 ● 実装例を多めに紹介 What is it?
Slide 3
Slide 3 text
3 ● この資料を作り始めるタイミングでぴっ たりの本を見つけた ● 今回はほぼこの本の紹介です ○ 書籍の実装例はCrystal ○ 本発表ではJava 参考資料
Slide 4
Slide 4 text
4 よい設計とは?
Slide 5
Slide 5 text
● 要件を満たすシステムの構造 を計画・構築する ○ アーキテクチャ(インフラ/ソフトウェア) ○ クラス ○ メソッド ● 品質を担保する 5 ソフトウェア設計
Slide 6
Slide 6 text
6 ● 機能の修正が簡単 ● 機能の追加が簡単 ● 修正範囲が明確 ● 影響範囲が明確 ● テストがしやすい ● リリーススピードが早い ● バグの原因追求がしやすい よい設計の指標(一例) 「よい設計」は状況によって異なる 例えば保守しない案件では、 コードの読みやすさや バグの原因追求のしやすさは 優先度が低いかもしれない
Slide 7
Slide 7 text
7 ● 機能の修正が簡単 ● 機能の追加が簡単 ● 修正範囲が明確 ● 影響範囲が明確 ● テストがしやすい ● リリーススピードが早い ● バグの原因追求がしやすい 今回考える「よい設計」の指標
Slide 8
Slide 8 text
8 ● 「機能の追加が簡単になるように実装するぞ!」 ● 何から着手する? どうやって「よい設計」を実現するのか? ・・・
Slide 9
Slide 9 text
9 ● 「機能の追加が簡単になるように実装するぞ!」 ● 何から着手する? ● ゴールに向かうために、 もう少し具体的な指針が必要 ● そのひとつが「SOLID原則」 どうやって「よい設計」を実現するのか?
Slide 10
Slide 10 text
10 SOLID原則
Slide 11
Slide 11 text
11 ● オブジェクト指向で用いられる五つの原則の頭字語 ● ソフトウェア設計をより平易かつ柔軟にして保守しやすくするもの ● Robert C. Martin が提唱した SOLID原則とは? Robert C. Martin またの名をボブおじさん(Uncle Bob)
Slide 12
Slide 12 text
12 SOLID原則 略称 英語名称 日本語名称 SRP Single Responsibility Principle 単一責任の原則 OCP Open-Closed Principle 開放閉鎖の原則 LSP Liskov Substitution Principle リスコフの置換原則 ISP Interface Segregation Principle インターフェース分離の原則 DIP Dependency Inversion Principle 依存性逆転の原則
Slide 13
Slide 13 text
13 ● クラスや関数は、ただひとつの機能について責任を持つ ● どこまでをひとつの機能とするべきかは状況による ○ 例)ファイルの読み書きを分けるか? 単一責任の原則 ● Single Responsibility Principle ● Open-Closed Principle ● Liskov Substitution Principle ● Interface Segregation Principle ● Dependency Inversion Principle 変更するための理由が、一つのクラスに対して一つ以上あってはならない いい言葉!
Slide 14
Slide 14 text
14 ● 拡張(機能追加)するときに既存コードを修正するべきでない ● すごくざっくり言うと、ポリモーフィズムを駆使して if文を減らそうって話 開放閉鎖の原則 ● Single Responsibility Principle ● Open-Closed Principle ● Liskov Substitution Principle ● Interface Segregation Principle ● Dependency Inversion Principle ソフトウェアの実体(クラス、モジュール、関数など)は、 拡張に対して開かれているべきであり、修正に対して閉じていなければならない
Slide 15
Slide 15 text
15 ● サブクラスは親クラスの代わりとして使えるべき ● 主に継承に適用される原則 ● かなり理論的な原則で、もう少し細かい定義がある ● 今日はあまり扱いません リスコフの置換原則 ● Single Responsibility Principle ● Open-Closed Principle ● Liskov Substitution Principle ● Interface Segregation Principle ● Dependency Inversion Principle ある基底クラスへのポインタないし参照を扱っている関数群は、その派生クラスの オブジェクトの詳細を知らなくても扱えるようにしなければならない リスコフさん→
Slide 16
Slide 16 text
16 ● インターフェースはこの機能を保証します、という「約束」 ● 保証できない機能の実装を強制してはならない ● 小さいインターフェースを組み合わせる インターフェース分離の原則 ● Single Responsibility Principle ● Open-Closed Principle ● Liskov Substitution Principle ● Interface Segregation Principle ● Dependency Inversion Principle 汎用なインターフェースが一つあるよりも、 各クライアントに特化したインターフェースがたくさんあった方がよい
Slide 17
Slide 17 text
17 ● 依存とは「使うこと」 ○ 使う側が使われる側に依存する ● より安定したものに依存しましょう ○ 安定=変更が少ない ○ 下位より上位が安定 ○ 具象より抽象が安定 依存性逆転の原則 ● Single Responsibility Principle ● Open-Closed Principle ● Liskov Substitution Principle ● Interface Segregation Principle ● Dependency Inversion Principle 上位モジュールはいかなるものも下位モジュールから持ち込んではならない。 双方とも具象ではなく、抽象(インターフェースなど)に依存するべきである
Slide 18
Slide 18 text
18 各原則の関係 原則 他の原則との関係 SRP 根底となる原則。適切な責務分割で修正範囲を限定する。 OCP クラス設計で目指す形。各クラスの関係性を工夫し 変更容易性を上げる。 LSP OCPのためのテクニック。サブクラスを交換可能にする方法。 ISP OCPのためのテクニック。クラス間のインターフェース定義の方法。 DIP OCPのためのテクニック。クラス間の依存方向をコントロールする方法。 1. SRPでクラスの責務をひとつにし、修正が他のクラスに及ばないようにする 2. LSP/ISP/DIP を用いて、修正・追加が容易なOCPを目指す
Slide 19
Slide 19 text
19 単一責任、開放閉鎖は基礎的・ わかりやすいので人気 依存性逆転は難しくてかっこいいの で好まれる (私も投票した) 余談:好きな原則
Slide 20
Slide 20 text
20 実装例
Slide 21
Slide 21 text
21 ● 設定を表現するクラス ○ ApiConfig:APIの設定を 保持しているクラス ○ DbConfig:DBの設定を 保持しているクラス ○ RedisConfig:Redisの 設定を保持しているクラス 本発表で扱う問題 ● 設定クラスを利用するクラス ○ Api:外部APIを呼び出すクラス ○ Db:DBからデータ取得するクラス ○ Redis: Redisからデータ取得するクラス ● データを取得して処理するクラス ○ UserRepository: Api/Db/Redis などのクラスを 使ってデータを処理する ユーザーの情報を 外部APIやDBから取得するシステム
Slide 22
Slide 22 text
22 問題設定 ● API、DBそれぞれにjson形式の設定ファイルがある ● 設定ファイルの中身は、どちらもidとpasswordがある わるい設計 ● 設定ファイルのidとpasswordを返すConfigManager ● idとpasswordを取得したいのは同じなので、ConfigManagerとして共通 化 1. 単一責任の原則( SRP) api_config.json db_config.json 変更するための理由が、一つのクラスに 対して一つ以上あってはならない
Slide 23
Slide 23 text
23 1. 単一責任の原則 typeに渡す文字列で 処理を分岐 idとpasswordの 取得は共通 DBクラスは省略 Main.java
Slide 24
Slide 24 text
24 1. 単一責任の原則 Main.java
Slide 25
Slide 25 text
25 1. 単一責任の原則 APIクラスからConfigManagerク ラスへの依存 Main.java
Slide 26
Slide 26 text
26 1. 修正が伝播する ○ 片方の責務の修正によって、もう片方にも修正が必要になる 2. 可読性とテスト容易性が低い ○ 可読性が悪く、テストが書きづらい わるい設計の問題点 変更するための理由が、一つのクラスに 対して一つ以上あってはならない
Slide 27
Slide 27 text
27 問題はConfigManager 変更理由としてあり得るもの ● api_config.jsonの形式が変わる ● db_config.jsonの形式が変わる これはSRPに違反している 修正が伝播する 変更するための理由が、一つのクラスに 対して一つ以上あってはならない
Slide 28
Slide 28 text
28 修正が伝播する api_config.jsonのkey名が 変更されたら、、、 ConfigManagerにも修正が 必要になって、、、 db_config.jsonに修正が 伝播する! 変更するための理由が、一つのクラスに 対して一つ以上あってはならない
Slide 29
Slide 29 text
29 可読性・ テスト容易性が低い 変更するための理由が、一つのクラスに 対して一つ以上あってはならない ● typeの値によってConfigManagerの意味が変わる ○ 処理を追わないと何が起こるかわからない ○ 可読性が低い状態 ● 機能追加で、typeの種類が増えるかも ○ あり得るパターンが増えると その動作を担保するテストも必要になる
Slide 30
Slide 30 text
30 1. プロパティが多い 2. クラスの責任を端的に言えない 3. プロパティで条件分岐する 4. テストが書きづらい 5. 修正が伝播する 単一責任の原則違反のにおい
Slide 31
Slide 31 text
31 1. プロパティが多い 2. クラスの責任を端的に言えない 3. プロパティで条件分岐する 4. テストが書きづらい 5. 修正が伝播する 単一責任の原則違反のにおい プロパティが多い場合、次の可能性が 高い ● 2つ以上の責任を表現しようとして いる ● プロパティを媒介に修正が伝播す る
Slide 32
Slide 32 text
32 1. プロパティが多い 2. クラスの責任を端的に言えない 3. プロパティで条件分岐する 4. テストが書きづらい 5. 修正が伝播する 単一責任の原則違反のにおい 幅広い意味を持つ名前のクラスは、様々な 責任を負わされる 「設定を管理する(ConfigManager)」はそ の典型例 もう一段階具体的に説明できないか 考えるべし
Slide 33
Slide 33 text
33 1. プロパティが多い 2. クラスの責任を端的に言えない 3. プロパティで条件分岐する 4. テストが書きづらい 5. 修正が伝播する 単一責任の原則違反のにおい flagやtypeなどのプロパティを持ち、 条件分岐しているもの プロパティによって「やりたいこと」が異な る →つまり責任が異なる 単一責任の原則にしたがってクラスを分 離する
Slide 34
Slide 34 text
34 1. プロパティが多い 2. クラスの責任を端的に言えない 3. プロパティで条件分岐する 4. テストが書きづらい 5. 修正が伝播する 単一責任の原則違反のにおい 複数の責任があると、テストケースが膨 大になる メソッドもシンプルになっているはずな ので、テスト自体も複雑にならない
Slide 35
Slide 35 text
35 1. プロパティが多い 2. クラスの責任を端的に言えない 3. プロパティで条件分岐する 4. テストが書きづらい 5. 修正が伝播する 単一責任の原則違反のにおい 複数の責任が一つのクラスに集まって いる 異なる責任の間でプロパティやメソッド を共有できてしまう ある責任に対する修正が、別の責任を 扱う箇所にも伝播する
Slide 36
Slide 36 text
36 責任にあわせてクラスを分割する よい設計への修正 設定ファイルを扱うクラスから条件分岐が消えた 設定を個別に変更でき、修正が伝播しない
Slide 37
Slide 37 text
37 責任にあわせてクラスを分割する よい設計への修正 依存関係も整 理された
Slide 38
Slide 38 text
38 問題設定 ● UserRepositoryは、データを取得してユーザー一覧を返す ● データの取得元にはAPIとDBがあり、切り替えられるようにする わるい設計 ● UserRepositoryに、APIクラスとDBクラスのプロパティを持たせる ● どちらを取得元にするかは、コンストラクタで設定する 2. 開放閉鎖の原則( OCP) ソフトウェアの実体は、拡張に対して開か れているべきであり、修正に対して閉じて いなければならない
Slide 39
Slide 39 text
2. 開放閉鎖の原則( OCP) 39 2種類の コンストラクタ APIクラスは省略 処理を分岐 Main.java
Slide 40
Slide 40 text
2. 開放閉鎖の原則( OCP) 40 UserRepositoryから API、DBへの依存 Main.java
Slide 41
Slide 41 text
41 1. 修正の影響を受ける範囲が広い 2. 追加の影響を受ける範囲が広い わるい設計の問題点 ソフトウェアの実体は、拡張に対して開か れているべきであり、修正に対して閉じて いなければならない
Slide 42
Slide 42 text
42 試しに、Db.selectAllUser()をDb.getUserList()に変更してみる 修正の影響を受ける 範囲が広い ソフトウェアの実体は、拡張に対して開か れているべきであり、修正に対して閉じて いなければならない UserRepositoryにも修正が伝 播してしまった!
Slide 43
Slide 43 text
43 API、DBの他に、Redisからもデータを取得したくなり、クラスを追加 追加の影響を受ける 範囲が広い ソフトウェアの実体は、拡張に対して開か れているべきであり、修正に対して閉じて いなければならない UserRepositoryにも修正が必要 =修正が閉じていない
Slide 44
Slide 44 text
44 インターフェースを導入 よい設計への修正 UserRepositoryから 条件分岐が消えた
Slide 45
Slide 45 text
45 インターフェースを導入 よい設計への修正 各データ取得元は インターフェースを実装 UserRepositoryは インターフェースに依存
Slide 46
Slide 46 text
データ取得元の修正や追加の影響が、UserRepositoryに及ばない 46 よい設計のメリット DBクラスの 関数名を変更しても、 DbAdapterで吸収できる Redisクラスを 追加する場合、 DataStoreInterface を実装していればOK
Slide 47
Slide 47 text
47 OCPを実現するテクニックの一つ 書籍での紹介もOCPのおさらいなので、今回は割愛 3. リスコフの置換原則 (LSP) ある基底クラスへのポインタを持つ関数 群は、その派生クラスの詳細を知らなくて も扱えるべき
Slide 48
Slide 48 text
48 問題設定 ● APIを実行するApiクラス、DBから データを取得するDbクラス ● それぞれの接続情報を ApiConfig、DbConfigに持つ わるい設計 ● 各Configクラスは ConfigInterfaceを実装 4. インターフェース 分離の原則( ISP) 一つの汎用なインターフェースより、 特化したインターフェースがたくさんあっ た方がよい
Slide 49
Slide 49 text
49 4. インターフェース 分離の原則( ISP) 一つの汎用なインターフェースより、 特化したインターフェースがたくさんあっ た方がよい どちらもConfigInterfaceを使用 ApiはgetId()を利用、DbはgetUser()を利用 getPassword()は共通 Main.java
Slide 50
Slide 50 text
50 4. インターフェース 分離の原則( ISP) 一つの汎用なインターフェースより、 特化したインターフェースがたくさんあっ た方がよい APIの設定ファイル DBの設定ファイル 設定を扱う共通の インターフェース 使わないメソッド
Slide 51
Slide 51 text
51 fatなインターフェースを媒体にして修正が伝播してしまう わるい設計の問題点 一つの汎用なインターフェースより、 特化したインターフェースがたくさんあっ た方がよい ①DbConfigのgetUser()を getUserName()に変更してみる ②DbのgetUser()がgetUserName()に変わる ②ConfigInterfaceのgetUser()が getUserName()に変わる ③ApiConfigのgetUser()が getUserName()に変わる!
Slide 52
Slide 52 text
責任ごとにインターフェースを分離する DB側の修正がAPI側に伝播しない 無理な共通化は悲劇の始まりです 見極めが重要(難しいけど、、) よい設計への修正 一つの汎用なインターフェースより、 特化したインターフェースがたくさんあっ た方がよい 52 分離と共通化って真逆でしょ? 共通化したほうがいいって 聞いたけど
Slide 53
Slide 53 text
53 問題設定 ● UserRepositoryは、APIでデータを取得してユーザー一覧を返す わるい設計 ● UserRepositoryに、APIクラスのプロパティを持たせる 5. 依存性逆転の 原則(DIP) 上位モジュールは下位モジュールから 何も持ち込んではならない 具象ではなく、抽象に依存するべき
Slide 54
Slide 54 text
5. 依存性逆転の 原則(DIP) 上位モジュールは下位モジュールから 何も持ち込んではならない 具象ではなく、抽象に依存するべき 54 Main.java
Slide 55
Slide 55 text
● DIPは依存の方向をコントロールする手法 ● 右のクラス図だけをみても、 設計の良し悪しを議論できない ● 議論のために、次の前提を導入する UserRepositoryはApiに依存しているので、Apiの変更はUserRepositoryに 伝播する 逆に、UserRepositoryの変更はApiに伝播しない わるい設計の問題点 上位モジュールは下位モジュールから 何も持ち込んではならない 具象ではなく、抽象に依存するべき 55 ● Apiの修正が頻繁に行われたり、他のクラスと頻繁に交換される
Slide 56
Slide 56 text
Apiが変更されたとしても、UserRepositoryは変更したくない 基本方針 ● 利用する側(UserRepository)の都合でインターフェースを定義 ● 利用される側(Api)はそのインターフェースを実装 よい設計への修正 上位モジュールは下位モジュールから 何も持ち込んではならない 具象ではなく、抽象に依存するべき 56
Slide 57
Slide 57 text
上位、下位のモジュールを考えてみる より抽象度の高いUserRepositoryを上位 より詳細な処理を行うApiを下位 上位モジュールで下位モジュールが利用されている =下位モジュールを持ち込んでいる状態 より詳細な処理を扱う下位モジュール(具象)に依存している よい設計への修正 上位モジュールは下位モジュールから 何も持ち込んではならない 具象ではなく、抽象に依存するべき 57
Slide 58
Slide 58 text
UserRepository(上位)の都合でインターフェースを定義し、 Api(下位)がそれを実装する Apiの変更が UserRepositoryに伝播しない よい設計への修正 上位モジュールは下位モジュールから 何も持ち込んではならない 具象ではなく、抽象に依存するべき 58 上位→下位の依存だった設計が 下位→上位の依存になった =依存性が逆転した 抽象(上位・インターフェース)に依存 している
Slide 59
Slide 59 text
よい設計への修正 上位モジュールは下位モジュールから 何も持ち込んではならない 具象ではなく、抽象に依存するべき 59 UserRepositoryとApiは インターフェースに依存 下位が上位に依存
Slide 60
Slide 60 text
● よい設計の指標は様々で、今回はクラス設計について、機能追加や修正 の容易さについて議論した ● SOLID原則は、よい設計の指標を実現するための考え方の一つ ● SRPでクラスの責務をひとつにし、修正が他のクラスに及ばないようにす る ● LSP/ISP/DIP を用いて、修正・追加が容易なOCPを目指す 60 まとめ
Slide 61
Slide 61 text
61 ● DRY原則 Don't Repeat Yourself ○ すべての知識はシステム内において、単一、かつ明確な、そして信頼 できる表現になっていなければならない ● KISSの原則 Keep It Simple, Stupid ○ 単純さを追求し、複雑さを避けること ● YAGNI原則 You Ain't Gonna Need It ○ 本当に必要になるまで作らない方がよい おまけ:その他の設計原則