保守性と拡張性を高めるためのSOLID原則とシステム設計
第1話:単一責任の原則(1)役割と責任の明確化
(最終更新日:2024.09.08)
(絵が小さい場合はスマホを横に)
1つのモジュールに1つの責任を!
単一責任の原則(SRP)は、ソフトウェア設計において各クラスやモジュールが一つの責務のみを持つべきであるという原則だ。
本記事では、SRPの重要性とその実践方法について詳述する。
複数の責任を持つクラスが引き起こす問題点を示し、リファクタリングを通じて単一責任を実現する方法を具体的な例を用いて解説する。
これにより、保守性と拡張性の高いシステム設計を目指す。
1.単一責任の原則とは
1.1 単一責任の原則(SRP)の定義
単一責任の原則は、クラスやモジュール、関数などのソフトウェアコンポーネントは「一つのこと」をするべきであり、
「一つの理由」でしか変更されるべきではないという原則だ。
ここで言う「一つのこと」というのは、単一の機能や役割を持つべきであることを意味する。
単一責任の原則は、ソフトウェア設計の大家であるロバート・C・マーチンによって提唱された。
彼は「クラスはただ一つの変更理由しか持つべきではない」と述べている。
1.2 背景と歴史
単一責任の原則は、オブジェクト指向設計(OOD)の一部として考えられている。
大規模なシステム開発では、ソフトウェアの各部分が複数の責任を持つと、修正や拡張が困難になり、予期しないバグが発生するリスクが高まる。
このため、責任を明確に分けることが重要とされている。
この原則は、ソフトウェアの複雑さが増すにつれて、保守性と拡張性を確保するための重要な指針として広く受け入れられるようになった。
特にアジャイル開発やCI/CD環境での素早い変更対応を可能にするためには、この原則が欠かせない。
予期せぬ不具合
1.3 単一責任の重要性
変更の容易さ:単一責任の原則に従って設計されたコンポーネントは、一つの変更理由しか持たないため、
特定の機能に対する変更が他の機能に影響を及ぼすリスクが減る。
これにより、システムの一部を変更する際に、他の部分を変更する必要がないため、バグが発生するリスクが低くなる。
再利用性の向上:単一責任を持つクラスやモジュールは、その責務が明確であるため、他のシステムやプロジェクトで再利用しやすくなる。
再利用性の高いコードは、開発効率を向上させるだけでなく、テストもしやすくなる。
デバッグとテストの簡素化:単一責任のコンポーネントは、特定の機能に対してのみ責任を負うため、バグの原因を特定しやすくなる。
また、テストもその機能に焦点を当てることができるため、テストケースの作成や実行が簡素化される。
1.4 単一責任の原則の適用例
下記はJavaで書いた悪い例と良い例である。
悪い例:例えば、ユーザー情報を管理するクラスが、データベースからユーザー情報を取得する責務と、
ユーザー情報をフォーマットして表示する責務の両方を持っている場合、これは単一責任の原則に違反している。
このクラスを変更する理由が複数(データベーススキーマの変更と表示フォーマットの変更)存在するためである。
悪い例(責務が2つ)
良い例:単一責任の原則に従う場合、このクラスはデータベースアクセスとフォーマット処理を分離する。 例えば、UserRepositoryクラスがデータベースアクセスを担当し、UserFormatterクラスがフォーマットを担当するようになる。
良い例(責務が1つずつ)
1.5 単一責任の原則と他のSOLID原則との関係
単一責任の原則は、他のSOLID原則と密接に関連している。
例えば、SRPはインターフェース分離の原則(ISP)や依存関係逆転の原則(DIP)と組み合わせることで、
さらに強力な設計原則として機能する。これらの原則を組み合わせることで、より堅牢で保守性の高いシステムを設計できる。
単一責任の原則は、ソフトウェアコンポーネントが持つべき責務を明確に定義するための基本的な原則だ。 この原則を適用することで、システムは保守性が高く、変更に強い設計になる。
2.役割と責任の明確化
このセクションでは、ソフトウェア設計において、各コンポーネントの役割と責任をどのように明確化し、 それが単一責任の原則にどのように結びつくかを解説する。
2.1 役割と責任の定義
役割は、ソフトウェアコンポーネント(クラス、モジュール、関数など)がシステム内で果たすべき役目を指す。
例えば、データベースアクセス、ユーザーインターフェースの表示、データのフォーマットなど、特定の機能やタスクに関する役割だ。
責任は、特定の役割に関連する具体的な義務や仕事を意味する。
ソフトウェアコンポーネントが持つ責任は、その役割を果たすために必要な機能や行動を含むものだ。
2.2 役割と責任を明確にする理由
各コンポーネントの役割と責任が明確であれば、システムの理解が容易になり、保守や拡張がしやすくなる。
変更やバグ修正が必要な際に、どの部分に手を加えるべきかが明確で、影響範囲を特定しやすくなる。
また、他のシステムやプロジェクトで再利用しやすくなる。
責任が一貫していれば、コンポーネントの信頼性も高まり、再利用時に予期しない副作用を避けられる。
仕事別に役割を分ける
2.3 役割と責任の分離の具体的な手法
モジュールの分割:大きなクラスやモジュールに複数の責任が集中している場合、それを小さなモジュールやクラスに分割し、
それぞれが一つの役割と責任を持つように設計する。例えば、データ取得とデータ表示を別々のクラスに分けるという具合だ。
命名規則の利用:クラスや関数の名前を見ただけで、その役割と責任が明確にわかるように命名することが重要だ。
例えば、UserRepositoryというクラス名は、ユーザーデータの管理を責任とすることが明確である。
インターフェースの定義:役割ごとに異なるインターフェースを定義し、それに従う形で実装を行うことで、各クラスやモジュールの責任を明確化する。
これにより、インターフェースを使って役割を明確に分離し、必要に応じて異なる実装を差し替えることができる。
2.4 単一責任の原則の適用例
下記はJavaで書いた悪い例と良い例である。
悪い例:例えば、たとえば、次のようなクラスは、複数の責任を持っているため、単一責任の原則に違反している。
このクラスは、ユーザー情報の管理とレポートの生成という二つの責任を持っている。
悪い例(責任が2つ)
良い例:単一責任の原則に従う場合、このクラスはデータベースアクセスとフォーマット処理を分離する。 例えば、UserRepositoryクラスがデータベースアクセスを担当し、UserFormatterクラスがフォーマットを担当するようになる。
良い例(責任が1つずつ)
2.5 役割と責任の明確化における注意点
役割と責任を分離することは重要だが、過度に分割しすぎると、システムが複雑になり、管理が難しくなる。
役割と責任の分離は適切なバランスを保つことが重要だ。
分割したクラスやモジュールが一貫性を保ち、システム全体で統一された設計を維持することが求められる。
各コンポーネントが独立して動作する一方で、全体としての調和を取ることが重要になる。
2.6 まとめ
役割と責任の明確化は、単一責任の原則を実現するための重要なステップだ。
各コンポーネントが一つの役割と責任に専念することで、システムの保守性、再利用性が向上し、変更に強い設計が可能になる。
役割と責任の定義、明確化の手法、具体例を通じて、単一責任の原則をどのように適用すべきかを理解しよう。
3.まとめ
第1回では、単一責任の原則(SRP)の基本概念とその重要性について解説した。 SRPは、各クラスやモジュールが一つの責務のみを持つべきであるという設計原則だ。 複数の責任を持つコードが引き起こす問題点を具体例で示し、リファクタリングを通じてどのように単一責任を実現できるかを紹介した。 今回学んだことを活かして、保守性と拡張性の高いプログラミングを行おう。