単一責任の原則
SOLID の S - クラスやモジュールが変更される理由は 1 つだけであるべきという設計原則
単一責任の原則とは
単一責任の原則 (Single Responsibility Principle, SRP) は、SOLID 原則の S にあたり、「クラスを変更する理由は 1 つだけであるべき」という設計原則である。Robert C. Martin が 2003 年の著書「Agile Software Development」で体系化した。
重要なのは、「1 つのことだけをする」という意味ではないことだ。Martin 自身が後に定義を修正し、「1 つのアクター (利害関係者) に対してのみ責任を持つ」と再定義している。ユーザー管理と請求処理を 1 つのクラスに混在させると、経理部門の要件変更がユーザー管理に波及するリスクがある。変更の理由が異なるコードを分離することが本質だ。
よくある誤解
「1 クラス 1 メソッド」ではない
SRP を極端に解釈して、クラスにメソッドを 1 つしか持たせないのは過剰分割だ。SRP が求めるのは「変更理由の単一性」であり、メソッド数の制限ではない。UserRepository が findById、findByEmail、save、delete を持つのは SRP に反しない。これらはすべて「ユーザーの永続化」という同一の責務に属する。
凝集度との関係
SRP と凝集度 (Cohesion) は表裏一体だ。SRP に従うと自然に凝集度が高くなる。逆に、凝集度が低いクラス (互いに無関係なメソッドが混在) は SRP に違反している可能性が高い。
違反の兆候
コードレビューで以下のパターンを見つけたら SRP 違反を疑う。
- クラスが複数の理由で変更される (「ユーザー管理の仕様変更」と「レポート形式の変更」が同じクラスに影響する)
- クラス名に「And」「Manager」「Handler」「Processor」が含まれる (
UserAndBillingManager) - メソッドが互いに無関係なデータを操作している
- テスト時に無関係なモックが大量に必要になる
- クラスのコンストラクタに注入する依存が 5 つ以上ある
実務での適用
// ❌ SRP 違反: 3 つのアクターの責務が混在
class Employee {
calculatePay(): number { /* 経理部門の要件 */ }
reportHours(): string { /* 人事部門の要件 */ }
save(): void { /* DBA の要件 */ }
}
// ✅ SRP 準拠: アクターごとに分離
class PayCalculator {
calculate(employee: Employee): number { /* ... */ }
}
class HourReporter {
report(employee: Employee): string { /* ... */ }
}
class EmployeeRepository {
save(employee: Employee): void { /* ... */ }
}
この例は Martin が「Clean Architecture」で挙げた典型例だ。Employee クラスの calculatePay を変更したとき、reportHours が壊れるリスクがある。責務を分離すれば、経理部門の変更が人事部門に影響しない。
SRP の適用レベル
SRP はクラスだけでなく、複数のレベルで適用できる。
| レベル | SRP の適用 | 例 |
|---|---|---|
| 関数 | 1 つの処理だけを行う | バリデーションと保存を分離 |
| クラス | 1 つのアクターに対して責任を持つ | PayCalculator と HourReporter |
| モジュール | 1 つのドメイン領域を担当する | 認証モジュールと課金モジュール |
| マイクロサービス | 1 つのビジネス機能を提供する | 注文サービスと在庫サービス |
マイクロサービスの境界設計は、SRP をサービスレベルに拡張したものと捉えられる。
過剰分割の罠
SRP を厳密に適用しすぎると、クラス数が爆発して逆に保守性が下がる。1 つの変更で 10 個のファイルを修正する状況は、分割が過剰な兆候だ。
判断基準は「実際に異なるタイミング・異なる理由で変更されるか」である。理論上は分離できても、実際には常に一緒に変更されるコードを無理に分割する必要はない。YAGNI 原則とのバランスを取る。
単一責任の原則の背景や設計思想は関連書籍に詳しい。