SOLID原則

英語名 SOLID Principles
読み方 ソリッド プリンシプルズ
難易度
所要時間 理解に1〜2週間、実践は継続的
提唱者 ロバート・C・マーティン(Uncle Bob)
目次

ひとことで言うと
#

S・O・L・I・D の頭文字で表される5つの設計原則。クラスやモジュールの責務を明確にし、変更に強く拡張しやすいコードを書くためのオブジェクト指向のバイブル。

押さえておきたい用語
#

押さえておきたい用語
SRP(Single Responsibility Principle)
単一責任の原則。1つのクラスには1つの変更理由だけを持たせるという原則のこと。「誰がこのコードの変更を要求するか」で責務を分ける。
OCP(Open/Closed Principle)
開放閉鎖の原則。拡張に対して開いていて、修正に対して閉じている設計である。新機能の追加で既存コードを変更しなくて済む状態を目指す。
LSP(Liskov Substitution Principle)
リスコフの置換原則。親クラスの代わりに子クラスを使っても、プログラムが正しく動くべきという原則を指す。
ISP(Interface Segregation Principle)
インターフェース分離の原則。クライアントが使わないメソッドに依存させない、つまり大きなインターフェースを小さく分割するという原則を指す。
DIP(Dependency Inversion Principle)
依存性逆転の原則。上位モジュールは下位モジュールに直接依存せず、両方とも抽象(インターフェース)に依存するという原則を指す。

SOLID原則の全体像
#

SOLID原則:5つの原則がコードの保守性と拡張性を支える
S単一責任1クラス1つの変更理由Single ResponsibilityO開放閉鎖拡張に開き修正に閉じるOpen/ClosedLリスコフ置換子クラスは親の代わりに使えるLiskov SubstitutionIIF分離大きなIFは小さく分割Interface SegregationD依存逆転抽象に依存するDep. Inv.5原則がもたらす効果変更に強い拡張しやすいテストしやすい適用前 vs 適用後SOLID適用前1クラス2000行、何でもやる直接newで密結合1箇所の修正 → 5箇所が壊れるSOLID適用後1クラス1責務、明確な役割インターフェース経由で疎結合新クラス追加だけで機能拡張変更に強い設計の土台Foundation for Maintainable Code
SOLID原則の学び方フロー
1
SRP(単一責任)
まず肥大クラスを「変更理由」で分割する
2
OCP(開放閉鎖)
新機能を既存コード変更なしで追加する設計
3
LSP・ISP
継承とインターフェースの正しい使い方を学ぶ
DIP(依存逆転)
抽象に依存させてテスタブルな設計に

こんな悩みに効く
#

  • 1つのクラスが肥大化して、どこを直しても別の場所が壊れる
  • 新しい機能を追加するたびに、既存コードを大量に書き換えなければならない
  • 「良い設計」の判断基準が自分の中にない

基本の使い方
#

ステップ1: S — 単一責任の原則(SRP)

1つのクラスには、1つの変更理由だけを持たせる

  • 「このクラスは何をする責任があるか?」を一言で言えるようにする
  • 複数の理由で変更されるクラスは、責務を分割する
  • 例:「注文の計算」と「注文のメール通知」は別クラスに

ポイント: 「変更理由」で考えるのがコツ。機能ではなく「誰がこのコードの変更を要求するか」で分ける。

ステップ2: O — 開放閉鎖の原則(OCP)

拡張に対して開いていて、修正に対して閉じている設計にする。

  • 新しい機能を追加するときに、既存コードを変更しなくて済むようにする
  • インターフェースや抽象クラスを使って拡張ポイントを作る
  • 例:新しい決済方法の追加は、新クラスの追加だけで対応

ポイント: Strategy パターンやPlugin パターンが典型的な実現手段。

ステップ3: L — リスコフの置換原則(LSP)

親クラスの代わりに子クラスを使っても、プログラムが正しく動くようにする。

  • サブクラスはスーパークラスの契約(事前条件・事後条件)を守る
  • 「正方形は長方形の一種」のような直感に反する継承を避ける
  • 継承より委譲(コンポジション)を検討する

ポイント: 「is-a」関係に見えても、振る舞いが変わるなら継承は使わない。

ステップ4: I — インターフェース分離の原則(ISP)+ D — 依存性逆転の原則(DIP)

ISP: クライアントが使わないメソッドに依存させない。大きなインターフェースは小さく分割する。

DIP: 上位モジュールは下位モジュールに依存しない。両方とも抽象に依存する。

  • 「太った」インターフェースは、利用者ごとに分割する
  • 具象クラスではなくインターフェースに依存する
  • DI(依存性注入)で実装を外部から渡す

ポイント: ISPとDIPはクリーンアーキテクチャの依存関係ルールの土台。

具体例
#

例1:ECサイトの通知システムでSOLID原則を適用する

状況: 従業員30名のECサイト運営企業。NotificationServiceという1つのクラスが2,400行に肥大化し、メール送信・Slack通知・プッシュ通知・ログ記録を全部やっていた。新しい通知チャネル追加に平均3日、バグ修正で他の通知が壊れることが月2〜3回発生。

SOLID適用:

原則BeforeAfter
SRPNotificationServiceが4つの責務EmailSenderSlackNotifierPushNotifierNotificationLoggerに分離
OCPLINE通知追加で既存コードを修正Notifierインターフェースを定義、LineNotifier追加だけで対応
LSPPushNotifierがトークンなしで例外トークン検証を別メソッドに切り出し、契約を統一
ISPNotifiersendBulk()が不要なクラスにもBulkNotifierとして別インターフェースに分離
DIPOrderServiceEmailSenderを直接newNotifierインターフェースに依存し、コンストラクタで注入

結果: 新しい通知チャネル追加が平均3日→4時間に短縮。通知起因のバグは月2〜3件→月0.2件に激減。

通知チャネル追加3日→4時間、通知起因バグ月2〜3件→0.2件。肥大化した1クラスをSOLID原則で分割しただけでこの結果。

例2:FinTech企業の決済処理でOCPを活用する

状況: 従業員80名のFinTech企業。決済処理にPaymentProcessorクラスがあり、クレジットカード・銀行振込の2種類に対応していた。新たにQRコード決済・暗号通貨決済の追加が決まったが、既存の巨大なswitch文に分岐を追加する方式に限界を感じていた。

OCP適用:

Before: PaymentProcessor内に巨大switch文(350行)
After:  PaymentMethod インターフェースを定義
        - CreditCardPayment implements PaymentMethod
        - BankTransferPayment implements PaymentMethod
        - QRCodePayment implements PaymentMethod(新規追加)
        - CryptoPayment implements PaymentMethod(新規追加)
指標BeforeAfter
新決済方法の追加工数5人日(テスト込み)1.5人日
追加時の既存コード変更行数平均120行0行(設定ファイル1行のみ)
決済関連バグ(月間)4.2件0.8件

新決済方法の追加工数5人日→1.5人日、既存コード変更行数0行。switch文をインターフェースに置き換えるだけで、この「触らずに拡張できる」設計が手に入る。

例3:医療系スタートアップがDIPでテスタビリティを確保する

状況: 従業員12名の医療系スタートアップ。患者データ管理システムのPatientServiceが外部の電子カルテAPI、保険API、薬歴APIに直接依存しており、テストを書こうとすると全APIが動いている必要があった。テストカバレッジはわずか8%。

DIP適用:

  • 各外部APIのインターフェースを定義(MedicalRecordRepository, InsuranceGateway, PrescriptionGateway
  • PatientServiceはインターフェースに依存し、具象クラスはDIコンテナで注入
  • テスト時はモック実装を注入、本番は実APIクライアントを注入
指標BeforeAfter
テストカバレッジ8%72%(3ヶ月後)
テスト実行時間12分(API依存)18秒(モック使用)
リリース前に発見できるバグ全体の20%全体の85%
本番障害件数(月間)6.3件1.1件

テストカバレッジ8%→72%、本番障害月6.3件→1.1件。外部依存をインターフェースに抽象化しただけでこの変化。品質要求の高い医療系だからこそ、DIPの効果が際立つ。

やりがちな失敗パターン
#

  1. 原則を教条的に適用する — すべてのクラスに無理やりインターフェースを作り、ファイル数が倍増。実際に変更が起きそうな箇所に重点的に適用するのが現実的
  2. SRPの「責任」を細かくしすぎる — 1メソッド1クラスのような極端な分割をしてしまう。「変更理由」が本当に異なるかを基準にする
  3. 原則の名前は知っているが実践できない — 理論だけ覚えて満足してしまう。既存コードのリファクタリングで練習するのが最も効果的
  4. 一度に全原則を適用しようとする — 既存コードベースに5原則を一気に適用しようとして大規模リファクタリングに突入する。まずSRPだけを意識して、肥大化した1クラスを分割するところから始める

まとめ
#

SOLID原則はオブジェクト指向設計の5つの指針。完璧に守ることが目的ではなく、「なぜこの設計にするのか」を考えるときの判断基準として使うのが正しい。まずはSRP(単一責任)から意識して、肥大化したクラスを分割するところから始めよう。