単一責任の原則

SOLID の S - クラスやモジュールが変更される理由は 1 つだけであるべきという設計原則

SOLID設計原則

単一責任の原則とは

単一責任の原則 (Single Responsibility Principle, SRP) は、SOLID 原則の S にあたり、「クラスを変更する理由は 1 つだけであるべき」という設計原則である。Robert C. Martin が 2003 年の著書「Agile Software Development」で体系化した。

重要なのは、「1 つのことだけをする」という意味ではないことだ。Martin 自身が後に定義を修正し、「1 つのアクター (利害関係者) に対してのみ責任を持つ」と再定義している。ユーザー管理と請求処理を 1 つのクラスに混在させると、経理部門の要件変更がユーザー管理に波及するリスクがある。変更の理由が異なるコードを分離することが本質だ。

よくある誤解

「1 クラス 1 メソッド」ではない

SRP を極端に解釈して、クラスにメソッドを 1 つしか持たせないのは過剰分割だ。SRP が求めるのは「変更理由の単一性」であり、メソッド数の制限ではない。UserRepositoryfindByIdfindByEmailsavedelete を持つのは 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 原則とのバランスを取る。

単一責任の原則の背景や設計思想は関連書籍に詳しい。

関連用語