SRP (Single Responsibility Principle) — SOLIDの最初の原則であり、各クラスまたはモジュールは変更する理由がまったく1つであるべきと定めています。この原則はロバート・マーチンによって著書 Clean Architecture (2017)で定式化され、モジュラーデザインの基礎となりました。この本によれば、SRPを適用することでコンポーネントの結合を直接減らし、機能修正時の連鎖変更を解消します。
主なポイント
SRP (Single Responsibility Principle) は単一責任原則であり、次のように定めています: 各クラスまたはモジュールは変更する理由がまったく1つであるべきです。これはクラスがまったく1つの操作だけを実行すべきという意味ではありません。あるアクターに対する単一の責任によって結ばれた関連アクションのグループを指します。
ロバート・マーチンはSRPをアクターの視点から再定義しました: クラスはある関係者または人々のグループからの要請によってのみ変更されるべきです。もし異なる2つのアクターが同じクラスに変更を要求する場合、責任は正しく分けられていません。
例えば、給与計算(経理部の要請)とレポート作成(経営層の要請)を同時に行うEmployeeクラスはSRPに違反します。計算ルールの変更がレポート作成に影響する可能性があり、その逆もあります。
モジュールは変更する理由が1つであり、1つのみであるべきです。変更理由はアクター(要件を発生させる人またはシステム)によって決定されます。異なるアクターからの要件が同じモジュールの変更につながる場合、そのモジュールはSRPに違反しています。
アクターの概念により、SRPは抽象的な推奨ではなく、アーキテクチャ分析の実用的なツールになります。システムを設計する際には、“このコードを変更するように請うのは誰か?”と問うだけです。もし複数の関係者がいる場合、責任を分けるべきです。
単一責任は、同じ理由で変更されるメソッドをグループ化することで実装されます。クラスは「スイスアーミナイフ」ではなく、関連するロジックの「集合ポイント」になります。これによりコードの理解が簡素になります: 開発者はクラスを見れば即座にその目的を理解できます。
SRPの仕組みは「単一変更軸」ルールに基づいています。もし機能性が独立した理由で変更できる場合、それらは別々のクラスに抽出されるべきです。これらのクラス間の結合は、コンポジションまたは委譲によって構築されます。
SRPの違反は「God Objects」と呼ばれる現象で表れます—さまざまなデータを扱う数十のメソッドを持つクラスです。こうしたクラスはテストが困難です—あるメソッドをテストするには他のすべてのメソッドに対する環境設定が必要です。ある責任を変更すると別のものが壊れ、コードが脆弱になります。
実践的には、SRPは開発者が“このコードはどこにあるのか?”という問いに答えるのに役立ちます。それぞれの責任が独自のクラスに分離されていれば、適切なファイルを見つけるのは数秒です。MVVMアーキテクチャのAndroidプロジェクトでは、UserViewModelはユーザー画面の状態のみを責任とし、UserRepositoryはデータ取得を責任とします。キャッシュロジックを探している開発者はViewModelsではなくUserCacheRepositoryへ行きます。こうしたコード組織は新しいチームメンバーのオンボーディングを加速し、リファクタリング中のエラー数を減らします。
モバイル開発はコードのモジュラ性に専門な要件をおきます。AndroidのFragmentやiOSのViewControllerは、タップ処理、APIコール、レスポンス解析、UI更新など、すべてのロジックがひとつのクラスに集中し、「磁石」になりがちです。SRPはこれらの責任を分離することを要求します。
Androidアーキテクチャでは、SRPはJetpackに関するGoogleの推奨事項に取り入れられています: ViewModelは画面状態、Repositoryはデータ、UseCaseはビジネスロジックを担当します。iOS開発では、MVVMおよびCoordinatorパターンが同じロジックに従います。
モバイルプロジェクトでSRPに従うことで、測定可能なメリットが得られます: クラスサイズの40-60%縮小、コードレビューの時間縮減、新機能追加時のリグレッションバグの減少です。独立したモジュールは単体テストによるカバレッジが容易で、他の画面での再利用も簡単です。
SRPに従うクラスの単体テストでは、モックオブジェクトが少なくても、構成も少なくてもいいです。クラスに単一の責任がある場合、依存関係は制限されます。テストは複数の無関係なシナリオの組み合わせではなく、ひとつの行動を検証します。
Google Testing Blog(2023)のレポートによれば、単一責任のクラスは、まとめクラスと比較して35%高いテストカバレッジを示しています。開発者は小さな理解しやすいモジュールに対してよりテストを書きたがります。
SRPに違反する、ある典型的なAndroidクラスを考えてみましょう—データを読み込み、レスポンスを解析し、UIを更新します。リファクタリング後は、それぞれの責任が独自のコンポーネントに分離されます。
// SRP違反: 1つのクラスがすべてを行う
class BadUserProfileActivity {
fun loadUser(userId: Int) {
// HTTPリクエスト
// JSON解析
// UI更新
// データベースに保存
}
}
// SRP適用後
class UserRepository {
fun getUser(userId: Int): User
}
class UserViewModel {
private val repo: UserRepository
fun loadUser(userId: Int) { }
}
class UserProfileFragment {
fun render(user: User) { }
}
iOS Swiftのネットワークレイヤーと表示を分離した類似の例:
// SRP違反: ViewControllerがデータとUIを管理
class BadProfileViewController: UIViewController {
func viewDidLoad() {
// URLSessionリクエスト
// JSONデコード
// label更新
}
}
// SRP適用後
protocol UserServiceProtocol {
func fetchUser(id: Int) async throws -> User
}
class ProfileViewModel {
private let service: UserServiceProtocol
func loadProfile(id: Int) { }
}
class ProfileViewController: UIViewController {
func display(user: User) { }
}
SRPリファクタリングはアーキテクチャを複雑にせず、責任を再分配するだけです。重複を削除することでコード量が減ることさえあります。新しいクラスには明確な目的があり、独立して開発できます。
コンポジションは、継承が不要な結合を生み出す場合にSRPを維持するのに役立ちます。数十のメソッドを持つスーパークラスの代わりに、サブクラスはコンストラクタを通じて専門オブジェクトのセットを受け取ります。各オブジェクトは自分の機能を担当します。
Android開発では、Decoratorパターンにより、元のクラスを変更せずに責任を追加できます。iOSでは、ネットワークレイヤーのMiddlewareチェーンにより、ログ取り、キャッシュ、認証を独立したモジュールに分離できます。
最もよくある違反は「God Class」です: データベースを管理し、通知を送信し、レポートを生成し、ユーザー入力を処理するクラスです。こうしたクラスはプロジェクトのボトルネックになり、変更ごとに完全なリグレッションテストが必要になります。
モバイル開発では、Activity、FragmentやViewControllerでビジネスロジックとUIロジックを混組することでSRPに違反します。onClickListenerがデータをバリデーションし、APIをコールし、ぼたんの可視性を同時に更新するとき—これは単一責任原則の直接的な違反です。
SRP違反の影響には以下があります: 並列開発の難しさ(1つのファイルでの競合)、単体テストの難しさ、変更のコストの高さ、コードの可読性低下。SRPの系統的な違反があるプロジェクトでは、新機能追加に2-3倍の時間がかかります。
SRP違反は間接的な予兆で識別できます: クラスが200行を超える、異なるアプリケーションレイヤー(UI + network + database)からモジュールをインポートする、異なるテーマで5つ以上のパブリックメソッドを持つ。結合度メトリクスは統計的な指標です: クラス内のメソッドの結合度が低いとSRP違反を示します。
SRP違反を検出するには、静的解析ツールを使用します: AndroidではTooManyFunctionsルールのDetekt、iOSではfile_lengthルールのSwiftLintです。これらのツールは、サイズや複雑さの制限を超えたクラスをハイライトします。
SRPに違反するクラスのリファクタリングはExtract ClassまたはExtract Delegateで行います: 関連するメソッドのグループを別のクラスに抽出し、元のクラスはコールを委譲します。これらのリファクタリングを次第に適用することで、God Classを、それぞれが単一の責任を持つ、結合の弱いモジュール群に変えられます。このアプローチにより、開発を止めずにアーキテクチャを改善できます — リファクタリングは繰り返し、一度に1つのモジュールずつ実行されます。
よくある質問
いいえ。SRPはメソッドの数ではなく、変更理由の数に関するものです。あるアクターに対する単一の責任を果たすのであれば、クラスは数十のメソッドを持つことができます。メソッドが1つだけというのは反対の極端で、コードの過度な分散を招きます。
同じ原則です。Single Responsibility Principleは「単一責任」でも「単一義務」でも訳されます。「責任」の方が本質をより正確に表しています: 技術的な機能ではなく、アクターに対する責任の問題だからです。
Repositoryは、データレイヤーにSRPを適用した直接的な結果です。データアクセスのロジックをViewModelやUseCaseに散らばす代わりに、Repositoryが単一の責任を担います: ソース抽象化でデータを提供することです。モバイルアーキテクチャにおけるSRPの古典的な実装です。
はい、SRPは依存関係を禁止していません。単一の責任を持つクラスは、コンポジションによって他のクラスに作業の一部を委譲できます。重要なのは、これら委譲されたタスクが同じ責任の一部であり、独立した変更理由ではないことです。
このように問いかけてください: “このクラスの変更を要求するアクターは誰か?”もし複数のアクターがいる場合、SRPは違反されています。また: クラスの目的を「そして」なしで一文で説明してみてください。できなければ、クラスがやりすぎています。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。