単一責任の原則
はじめに
ソフトウェア開発において、設計の原則を理解し、それを実践することは高品質なコードを維持する
ために非常に重要です。
その中でも「単一責任の原則(Single Responsibility Principle, SRP)」は、オブジェクト指向プログラミングにおける重要な原則の一つです。
単一責任の原則とは?
単一責任の原則は、クラスやモジュールが「単一の責任」を持つべきだとする原則です。
(クラスやモジュールが一つの理由で変更されるべき
であり、複数の異なる理由で変更されるべきではないとされています)
これは、コードの保守性や理解性を向上させることを目的としています。
すべてのクラスやモジュールが一つの役割に集中し、できる限りその役割を完全にカプセル化するべきです。
コード例
・仕様イメージ
画面に一覧があり、一覧の各レコードには入力項目がある。
各レコードにチェックを入れると更新対象となる。
入力値に異常がないレコードのみ更新する。
特殊: 社外ユーザが更新する場合、発報されるように通知フラグをONにする
-
原則違反 ※粒度が大きすぎる
public void update(List<InputForm> list) { for (InputForm item : list) { // チェックされたレコードだけを更新する if (item.isChecked) { // 妥当性判定をして問題ないレコードだけを更新する if (validate(item)) { // 社外ユーザの場合、通知フラグをONにする if (User.権限 === "社外") { item.通知フラグ = true; } else { item.通知フラグ = false; } // データを更新する save(item); } } } }
-
原則準拠 ※各ブロックは一つの役割で閉じている
public void update(List<InputForm> list) { // チェックされたレコードに絞り込む List<InputForm> checkedList = new ArrayList() for (InputForm item : list) { if (item.isChecked) { continue; } checkedList.add(item); } // 妥当な値のレコードに絞り込む List<InputForm> validList = new ArrayList() for (InputForm checkedItem : checkedList) { if (validate(checkedItem)) { continue; } checkedList.add(checkedItem); } // レコードの値を上書く for (InputForm validItem : validList) { // 社外ユーザの場合、通知フラグをONにする validItem.通知フラグ = !User.is社員; } if (validList.size() < 1) { return; } // データを更新する save(validList); } /** クラス・関数分割する、Stream使うとか色々あるけどここでは触れない */
利点
コードの理解容易性の向上
単一責任の原則に従うことで、各クラスやモジュールが明確な責任を持つため、コードの役割がはっきりします。
-
役割の明確化
- 各クラスやモジュールが一つの役割に専念するため、コードの意図や機能が理解しやすくなります。
-
学習コストの低減
- 新しい開発者がコードベースを理解する際に、複雑さが軽減され、学習コストが低くなります。
改修の影響範囲の局所化
単一責任の原則を適用することで、クラスやモジュールが単一の責任を持つため、変更が必要な場合の影響範囲が限定されます。
-
バグのリスク低減
- クラスやモジュールの変更が他の部分に影響を与えにくくなるため、新たなバグのリスクが低くなります。
-
変更への対応
- 単一の責任を持つ部品は、他の部品と独立して変更できるため、柔軟性が向上します。
テスト容易性の向上
単一責任の原則に従うことで、クラスやモジュールが小さく保てるため、テスト容易性が向上します。
-
テストの容易さ
- 単一の責任を持つクラスやモジュールは、テストが容易になり、テストケースがシンプルになります。
実践
クラスやモジュールの分割方法
クラスやモジュールが複数の責任を持っていると感じた場合、次のステップで分割を検討します。
-
責任の明確化
- クラスやモジュールが現在持っている責任をリストアップし、それぞれの責任を明確にします。
-
分割の基準
- 責任が異なる場合は、クラスやモジュールをそれぞれの責任に基づいて分割します。
- たとえば、データの処理とログの管理が同じクラスに含まれている場合、それぞれの機能を別々のクラスに分割します。
- 責任が異なる場合は、クラスやモジュールをそれぞれの責任に基づいて分割します。
-
インターフェースの定義
- 分割したクラスやモジュールのインターフェースを定義し、他の部分とどのように通信するかを明確にします。
役割に基づく設計
クラスやモジュールの設計を行う際には、以下のポイントを考慮して役割に基づく設計を行います。
-
関心の分離
- クラスやモジュールが単一の関心に集中するように設計します
- これにより、機能が明確になり、コードの理解や保守が容易になります。
- クラスやモジュールが単一の関心に集中するように設計します
-
役割の定義
- クラスやモジュールがどの役割を担うかを明確にし、それに従った設計を行います。
- 役割が曖昧な場合は再評価し、責任を明確に分けます。
- クラスやモジュールがどの役割を担うかを明確にし、それに従った設計を行います。
注意点
責任の定義の重要性
単一責任の原則を効果的に実践するためには、クラスやモジュールの責任を正確に定義することが重要です。
-
責任の曖昧さ
- 責任が曖昧な場合、クラスやモジュールの設計がうまくいかないことがあります。
- 責任は具体的かつ明確に定義する必要があります。
- 責任が曖昧な場合、クラスやモジュールの設計がうまくいかないことがあります。
-
責任の粒度
- クラスやモジュールが担うべき責任の粒度を適切に設定することが重要です。
- 粒度が大きすぎる場合、利点の効果が得られません。
- 粒度が小さすぎる場合、可読性が低下します。
- クラスやモジュールが担うべき責任の粒度を適切に設定することが重要です。
-
再評価の必要性
- 開発が進むにつれて、クラスやモジュールの責任が変わることがあります。
- その際には、責任の定義を再評価し、適切に調整します。
最後に
単一責任の原則は、最も難しい原則の1つですが、ソフトウェア設計において非常に強力なツールです。
この原則を理解し、実践することで、コードの品質を向上させるだけでなく、開発プロセスをより効率的に進めることができます。
Discussion