🔖

単一責任の原則

2024/08/20に公開

はじめに

ソフトウェア開発において、設計の原則を理解し、それを実践することは高品質なコードを維持するために非常に重要です。
その中でも「単一責任の原則(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