SOLID原則: システム設計の実践ガイド

solid principlessoftware architectureobject oriented designcode qualitymaintainability
SOLID原則: システム設計の実践ガイド

SOLIDに関する最も広まっているアドバイスは、同時に最も役に立たないものでもあります。それは「5つの原則を常にあらゆる場所に適用すれば、アーキテクチャはクリーンに保たれる」というものです。

これはカンファレンスの講演では聞こえがいいでしょう。しかし本番システムではすぐに破綻します。高スループットなサービス、データプラットフォーム、統合が多いエンタープライズのコードベースでは、硬直的なSOLIDは誰も必要としない抽象化の層、単純なロジックを覆い隠すインターフェース、そして主に会議を増やすだけのファクトリを生み出すことになりかねません。

SOLID原則は今なお重要です。非常に重要です。ただし、純粋さを試すテストとしてではなく、意思決定のためのツールとして使うときに最も効果を発揮します。優れたエンジニアは、明確な境界、予測可能な変更経路、そして副作用の少ないコードを生み出すためにSOLIDを活用します。同時に、いつ抽象化をやめるべきか、いつ層を統合すべきか、そしていつ直接的な実装のほうがクリーンな選択肢になるかも心得ています。

目次

SOLIDはドグマではなくツールである

SOLIDへの盲目的な追従は、コードベースをかえって悪化させることがあります。多くのチームがこれを遅れて学ぶ部分であり、たいていは誰かが、呼び出し元が1つしかないクラスを変更しないために、3つのインターフェース、2つのファクトリ、そしてプロバイダ層を導入してしまった後のことです。

壊れた鎖と電球のイラストが描かれた電気回路図の上に、レンチを持つ手がかざされている。

うまく使えば、SOLID原則は耐久性のあるソフトウェアを生み出します。より小さな振る舞いの単位、より明確な依存関係の境界、そしてシステム全体に被害を広げることなく変更を吸収できるコードへとチームを導きます。だからこそ、数十年にわたって重要であり続けてきたのです。

これらを使うことには、具体的な品質面での論拠もあります。ここで引用されているSoftware Engineering Instituteのデータによれば、5つのSOLID原則すべてを正しく守ることは、システム上の欠陥を30%削減することと相関しています。これは、すべての行に儀式的な抽象化が必要だという意味ではありません。規律ある設計は、時間の経過とともにより少ない欠陥を生む傾向がある、という意味です。

SOLIDが本来何のためにあるのか

SOLIDは、システムが次のような特性を1つ以上持つときに最も役立ちます。

  • 頻繁な変更: ビジネスルール、統合、ワークフローが変化し続ける。
  • 複数の開発者: 引き継ぎを経ても残る境界がコードに必要である。
  • 運用上のプレッシャー: 障害を隔離し、迅速に修正しなければならない。
  • テスト容易性の要求: 振る舞いをモック化、スタブ化、検証しやすくすべきである。

実践的なルール: ある原則が次の変更をより安全にし、実行時の振る舞いをそれ以上不透明にしないなら、その原則は残しましょう。単に形式的な手続きを増やすだけなら、疑ってかかりましょう。

ドグマ的なSOLIDが間違えていること

ドグマ的なSOLIDは、間接化を品質と取り違えることがよくあります。5つのファイルに分割されたクラスが、自動的によりクリーンになるわけではありません。2つ目の実装が存在する前に作られたインターフェースが、自動的に将来性を持つわけでもありません。ホットパスにレイテンシを加える完璧に疎結合な設計が、自動的により優れたエンジニアリングになるわけでもありません。

目標は耐久性のあるアーキテクチャです。つまり、理解でき、プレッシャーの中でもテストでき、四半期ごとに作り直さずとも進化させられるコードのことです。SOLIDはその目標を支えますが、それ自体が目標なのではありません。

5つのSOLID原則の解説

SOLIDに関する最短で役に立つ説明はこうです。各原則は、それぞれ異なる種類の複雑さを制御する助けになります。あるものは肥大化したクラスを狙い、別のものは壊れやすい拡張を狙い、さらに別のものは振る舞いの正しさを守ります。それらが組み合わさることで、より狭く、より予測可能な形で変化するコードが形作られます。

保守性、柔軟性、拡張性の高いソフトウェアアーキテクチャをプログラミングで構築するための、5つのSOLID原則を図解したダイアグラム。

単一責任の原則

SRPは、万能ナイフではなく適切なドライバーを選ぶことだと考えてください。ナイフは多くのことができます。しかしたいてい、そのすべてを下手にこなします。

クラスがビジネスルール、永続化、フォーマット処理、リトライ、ロギングを1か所で扱っているとき、そのクラスはSRPに違反しています。よくある「ビフォー」は、注文を検証し、データベースに書き込み、メールを送信し、監査メッセージを構築するOrderServiceのような姿です。「アフター」はそれらの関心事を専門的な協働オブジェクトに分割するため、メールのフォーマットの変更が注文検証を危険にさらすことはなくなります。

実践的な利点は、診断が容易になることです。ここで引用されている2025年のIEEE Softwareのレポートによれば、単一責任の原則を適用することでデバッグ時間が最大40%削減されます。これは日々のエンジニアリングの実態とも一致します。ユニットが1つの仕事だけをこなすなら、障害が現れる面は小さくなります。

SRPを検査する具体的な方法の1つがNOMメトリクスです。開発者は、このSRP測定に関する論文で説明されているように、クラス内のメソッド数nを数え、それらのメソッドにまたがる異なるパラメータ型Tを収集することで、クラスの適合度を計算できます。魔法のスコアではありませんが、有用な問いを突きつけてくれます。すなわち、このAPIにはいくつの責任が隠れているのか、という問いです。

開放閉鎖の原則

OCPとは、安定したコードを書き換えることなく振る舞いを拡張できるべきだ、という意味です。身近なたとえは電源タップです。新しい機器はプラグを差し込むだけで追加でき、壁を開けて建物の配線をやり直す必要はありません。

よくある「ビフォー」は、条件分岐だらけのセレクタです。

  • if provider == Stripe
  • else if provider == PayPal
  • else if provider == Adyen

新しいプロバイダを追加するたびに、古いロジックを編集することになります。これはすでに動いているコードにリスクを持ち込みます。「アフター」は共有される契約を導入し、各プロバイダが自身の戦略を実装します。セレクタは実装を解決するだけです。既存のフローは手つかずのまま残ります。

OCPは、決済手段、配送業者、エクスポート形式、サードパーティ統合など、要件が増えることがわかっている場所で最も強力です。逆に、おそらく決して変わることのない振る舞いのために拡張点を発明するのは無駄です。

リスコフの置換原則

LSPは、振る舞いの誠実さに関する原則です。あるサブタイプが基底型の代わりに使われるなら、システムは依然として正しく振る舞うべきです。

典型的な失敗は、図の上ではきれいに見えても実行時に破綻する継承です。サブクラスがより弱い保証でメソッドをオーバーライドし、基底の契約では投げなかった例外を投げたり、意味を予期せず変えたりします。コードはコンパイルされますが、呼び出し元はその抽象を信頼できません。

単純な「ビフォー」の例は、書き込みのサポートを保証する基底クラスStorageWriterがあり、その派生クラスが特定の書き込みに対してNotSupportedExceptionを投げる、というものです。「アフター」は偽りの継承を取り除き、能力を直接モデル化します。クラスは自分が果たせる契約のみを実装します。

サブクラスが利用可能になるために但し書き、特殊ケースのチェック、あるいは防御的なコメントを必要とするなら、その階層構造はおそらく嘘をついています。

インターフェース分離の原則

ISPは、クライアントが使わないメソッドに依存すべきではない、と述べています。たとえるなら制御パネルです。オペレータが必要とするのが開始と停止だけなら、無関係な12個のスイッチを渡すのは悪い設計です。

「ビフォー」は、生成、エクスポート、アーカイブ、権限、通知のためのメソッドを備えたIReportManagerのような幅広いインターフェースです。利用側はPDFを生成するだけであっても、その全体に依存することになります。「アフター」はそれをIReportGeneratorIExporterINotifierといった、より小さなインターフェースに分割します。

ISPは2つの場所で報われます。1つ目は、テストが必要な振る舞いだけを実装すればよくなるため、モックが単純になることです。2つ目は、各依存関係がより狭い目的を明示するようになるため、API契約が理解しやすくなることです。

依存性逆転の原則

DIPは、チームが規模の拡大に伴って最初に実感することが多い原則です。高レベルのモジュールは、低レベルの詳細に直接依存すべきではありません。どちらも抽象に依存すべきです。

「ビフォー」は、具体的なRedisクライアント、具体的なメール送信機、具体的なSQLリポジトリをインスタンス化するアプリケーションサービスのような姿です。そのサービスは今やビジネスロジックとインフラの選択の両方を抱え込んでいます。「アフター」はICacheINotificationSenderIOrderRepositoryといった抽象を注入するため、サービスはオーケストレーションとルールに集中できます。

DIPは、すべてのクラスに対してインターフェースを作れという命令ではありません。変動性を隔離するための手段です。依存関係が変わりやすい、環境によって異なる、あるいはテストでモック化する必要があるなら、抽象化が役立ちます。意味のある変動のない安定したユーティリティであれば、直接使用でも問題ないことがよくあります。

実践におけるコードとアーキテクチャの例

最も役に立つSOLIDの例は、おもちゃのようなBirdクラスではありません。それらは、特に統合、サービス境界、データ移動をめぐって、乱雑なビジネスシステムの中に現れます。

統合選択ロジックのリファクタリング

よくあるエンタープライズのパターンは、設定駆動の条件分岐ブロックから始まります。1つのクラスがテナント設定を読み取り、統合先を選び、ペイロードをマッピングし、リクエストを送信し、リトライを処理します。これは4つ目、5つ目の統合が現れるまでは機能します。

その種のコードは、たいてい次のように成長していきます。

段階 コードの見た目 何がまずくなるか
初期 if/elseによる統合分岐を持つ1つのサービス リリースは速いが、拡張が難しい
成長期 分岐が増え、プロバイダ固有のマッピングがサービス内に入り込む 新しい経路を追加するとリグレッションが起きる
リファクタ後 プロバイダインターフェースと個別の実装 拡張がクリーンになり、テストが隔離される

最近のあるプロジェクトはこのパターンをたどりました。多くの統合先があり、システムは設定に基づいてどれを使うかを選ばなければなりませんでした。疎結合なクラスが決定的な違いを生みました。選択ロジックが具体的な実装ではなく抽象に依存するようになると、チームは中核のオーケストレーションを書き換えることなく、プロバイダを追加したり差し替えたりできるようになりました。

ここでOCPとDIPが連携して働きます。OCPは選択の経路を安定させます。DIPはオーケストレーション層が、すべての統合の細部を知らずに済むようにします。

クラスレベルを超えてSOLIDを使う

SOLIDは、個々のクラスの外側でも重要です。

マイクロサービスでは、SRPがサービス境界の定義に役立ちます。あるサービスが、たまたま同時にローンチされたという理由で課金、レポート、認証を扱っているなら、そのデプロイと障害の面は入り混じってしまいます。責任をドメインごとに分割することで、より優れた変更管理が生まれます。

APIでは、ISPがより狭い契約へと導きます。バックエンドチームが1つの「柔軟な」スキーマを求めたからといって、利用者向けのエンドポイントが万能なペイロードを公開すべきではありません。焦点を絞った契約は利用者を独立に保ち、偶発的な結合を減らします。

データプラットフォームでは、DIPがパイプライン設計を改善します。エクストラクタ、トランスフォーマ、ローダは、ベンダー固有の詳細ではなく安定した内部契約に依存できます。これは、ウェアハウス、メッセージブローカー、あるいはファイル出力先が時間とともに変わる場合に特に有用です。

公開されたあるケーススタディは、規律ある設計が苦境にあるコードベースをどう整理できるかを示しました。この重複危機のケーススタディで説明されているように、重複に悩まされていたプロジェクトにSOLID原則を適用したところ、コードの重複が54%以上削減されました。ここから得られる教訓は「すべてを抽象化せよ」ではありません。繰り返されるロジックは、たいてい境界の欠如を示している、ということです。

そうした種類のシステム上の課題に取り組むチームにとって、エンタープライズソフトウェア設計の仕事は、SOLIDをクラスのリファクタリングの際だけでなく、コードとアーキテクチャの両方のレベルで適用したときに最も恩恵を受ける傾向があります。

SOLIDを適用する際の現実的なトレードオフ

「SOLIDを強制すべきか」という問いへの正直な答えは、「イエス、ただし機械的にではなく」です。

自分たちのコードベースが混沌としているために、すぐに成果を得るチームもあります。一方で、早すぎる抽象化によって自らの足を引っ張るチームもあります。この違いは、原則をどこに適用するか、そして抽象化のコストが見込まれる変更によって正当化されるかどうかから生まれます。

ソフトウェア開発におけるSOLID原則適用の現実的なトレードオフと利点を図解したレーダーチャート。

SOLIDが元を取れる場面

チームがSOLIDをうまく徹底すると、機能の実装には最初のうち時間がかかるかもしれません。それは普通のことです。境界に名前を付け、責任を分割し、契約を定義することに、より多くの時間を費やすからです。その見返りは後から訪れます。変更が広範囲のリファクタリングを引き起こさなくなるのです。

実際には、チームは次のようなトレードオフに気づくことがよくあります。

  • 初期の機能提供が遅くなる: よりクリーンな抽象化には設計の時間がかかる。
  • 後工程のバグが減る: 責任が隔離されることで影響範囲が縮小する。
  • リファクタリングの痛みが減る: 実際に変動がある場所には、すでに拡張点が存在している。
  • レビューの質が上がる: 責任と依存関係が検査しやすくなる。

ここでもテスト戦略が重要になります。優れた抽象化があっても、優れた検証がなければ隙間は残ります。モジュール化された設計とQA自動化の実践を組み合わせるチームは、契約が継続的に実行されるため、たいていSOLIDからより多くの価値を引き出します。

エンジニアリングの判断: 新しい抽象化によって機能の実装が遅くなるとしても、既知のホットスポットで将来の繰り返しの編集を取り除けるなら、それは良いトレードです。

抽象化が痛手になり始める場面

負の側面は現実に存在します。高性能なシステムでは、過剰な抽象化は単に煩わしいだけではありません。運用上のコストになり得ます。このSOLIDのトレードオフに関する議論によれば、SOLIDへの準拠のために過剰に抽象化すると、毎秒1万件を超えるリクエストを処理するC#/.NETのマイクロサービスにおいて、メモリのオーバーヘッドが12〜18%、レスポンスのレイテンシが8〜14%増加します

これは、シニアエンジニアが.NETやJavaのシステムで日常的に目にするものと一致します。「abstractfactorybuilderprovider」というジョークが存在するのには理由があります。チームは、ユーザーが必要とする振る舞いを出荷することよりも、完璧な抽象化の組み合わせを議論することに多くの労力を費やすことがあります。

いくつかの警告サインは、設計が一線を越えてしまったことを示します。

  • 根拠のない抽象化: 2つ目の実装も現実的な拡張経路もないのにインターフェースが存在する。
  • ホットパスの間接化: リクエストにとって重要なフローが、ビジネス価値を何も加えない層を経由して跳ね回る。
  • 命名のインフレ: クラスが振る舞いよりもパターンを表す名前になっている。
  • レビューの麻痺: エンジニアが、正しさ、障害処理、実行時コストよりも、抽象化の形について言い争う。

最もクリーンな修正が引き算であることもあります。死んだ層を取り除きましょう。使われていないインターフェースを統合しましょう。場合によっては、無関係なロジックの絡まりを独自のサービスに移し、メインのシステムがよりシンプルな形を取り戻せるようにしましょう。

よくあるアンチパターンとSOLID違反

ほとんどのSOLID違反は、その姿を知ってしまえば見つけるのは簡単です。問題は、チームがそれらを設計上の負債ではなく、普通のコードとして扱ってしまうことがよくある点です。

もつれ合ったコードの配線に虫眼鏡が焦点を合わせ、さまざまな警告やエラーの記号が描かれている。

レビューで見えるダメなSOLIDの姿

まずはSRPから。そのアンチパターンは神クラスです。入力を検証し、APIを呼び出し、モデルを変換し、データを永続化し、ログを書き込みます。たいていはスクロールするだけで見つけられます。1つのファイルが複数の関心事を扱っているなら、いずれ誰かが一部を変更して別の部分を壊すことになります。

OCPについては、決め手となるのは終わりのない分岐の連鎖です。新機能が追加されるたびに、型、プロバイダ、モード、リージョンに対する条件分岐がもう1つ増えます。システムは、リスクの高い中心的なロジックを変更することによってのみ拡張可能になっていきます。

LSP違反はより微妙です。NotImplementedExceptionを投げるサブクラス、親の契約が値を約束している場所でnullを返すサブクラス、あるいは呼び出し元に特別なルールの理解を要求するサブクラスに注意しましょう。子が安全に親を置き換えられないなら、その継承モデルは間違っています。

手早いレビューのチェックリストが役立ちます。

  • SRPについて: そのクラスに変更する理由が2つ以上ないかを問う。
  • OCPについて: サポートするバリアントが増えるたびに増えていく条件分岐を探す。
  • LSPについて: 派生型が保証を弱めたり、期待される振る舞いを変えたりしていないか確認する。
  • ISPについて: 実装側に無関係なメンバーのスタブ化を強いる幅広いインターフェースを見つける。
  • DIPについて: インフラの依存関係を直接生成している高レベルサービスに印を付ける。

AI生成コードには精査が必要

AIツールは、あるアンチパターンをより一般的にしました。もっともらしいスロップ(粗悪)コードです。コンパイルは通り、見慣れたパターンに従い、それでいて責任の混乱と偶発的な結合の散らかりを残します。

このことは、人間によるレビューをより重要にします。決して軽視できるものではありません。役に立つチームのルールはシンプルです。エージェントが作った後は自分のコードを見ること。何でもかんでも受け入れないこと。プロジェクトの将来について考えること。

AIの出力は、素早く書かれたジュニアの下書きのように扱いましょう。本番に届く前に、責任、契約、障害モードをレビューしましょう。

ここでSOLID原則が役立つのは、レビュアーに共通の語彙を与えてくれるからです。「なんとなく散らかっている感じがする」と言う代わりに、「このサービスはSRPに違反しており、インフラを直接インスタンス化しているので、DIPも満たしていない」と言えるようになります。

SOLIDで耐久性のあるシステムを構築する

耐久性のあるシステムは、ルールだけから築かれるものではありません。それは、再現可能な設計上の判断から築かれます。SOLID原則が役立つのは、有用な問いを突きつけてくれるからです。何が一緒に変わるのか、何が安定したままであるべきか、呼び出し元が信頼できる契約はどれか、そしてどの依存関係が隔離に値するのか、という問いです。

これは、信頼性が可視性と運用上の明快さに依存する本番システムにおいて、いっそう重要になります。きれいなクラス図は、観測もデバッグも不可能なサービスを救ってはくれません。設計は、負荷、障害、そして日常的な保守の下でも持ちこたえなければなりません。だからこそ、アーキテクチャの仕事は、トレーシング、メトリクス、ログといった実行時のフィードバックと並んで位置づけられるべきなのです。その側面については、強力なオブザーバビリティの実践が抽象化を誠実に保ってくれます。

チームが、なぜある境界、依存関係、あるいはサービス分割を別のものより選んだのかを記録するための軽量な方法を必要とするとき、実用的な参考資料となるのが、SpecStory, Inc.によるアーキテクチャ決定記録に関するこのガイドです。これはSOLIDとよく馴染みます。どちらも時間の経過とともに偶発的な複雑さを減らすことを目指しているからです。

SOLIDの最良の使い方は、着実で地味なものです。変更を狭め、正しさを高め、運用を穏やかにする場所に適用しましょう。そして、性能、シンプルさ、あるいはシステムの形が別の一手を求めるなら、柔軟に曲げましょう。

SOLID原則に関するよくある質問

統合が多いプロジェクトでSOLIDは役立つか

はい。そうした場面でこそ最も役立つことがよくあります。

最近の統合の多いあるプロジェクトには、設定によって選択される多くのプロバイダの選択肢がありました。疎結合なクラスがそれを実現可能にしました。各統合が明確な契約の背後に置かれると、チームは中心的な決定フローを書き換えることなく、実装を追加したり調整したりできるようになりました。それによってリファクタリングのプレッシャーが減り、バグは変更されているプロバイダに封じ込められました。

チームが最も苦労する原則はどれか

実際のところ、最も難しいのはSRPやDIPを暗記することではありません。スロップコードに抗うことです。

これはAI支援の開発ではいっそう当てはまります。チームは、生成されたコードを意図を持ってレビューする必要があります。基準は「動くこと」であってはなりません。基準は「動き、なおかつその構造が今後6か月の変更で自分たちを苦しめないこと」であるべきです。

原則を破ってもよいのはどんなときか

原則に従うことがシステムをかえって悪化させる場合には、破っても構いません。

これはたいてい、性能に敏感な経路や、実際の必要を超えて抽象化されてしまったコードで起こります。最良の修正が、層を取り除くこと、抽象化を統合すること、あるいは複雑さの絡まりを別のサービスに分割することであることもあります。肝心なのは、そのトレードを実行時の振る舞いと将来の保守を念頭に置いて、意図的に行うことです。

SOLIDはどこから来たのか

このSOLID原則の歴史で概説されているように、SOLID原則は、1995年にRobert C. Martinによって統一された頭字語として正式に導入されました。これは、それまでの10年間にわたって発展してきた5つのオブジェクト指向設計の原則を組み合わせたもので、1988年の開放閉鎖の原則や1987年のリスコフの置換原則が含まれています。

これらが長続きしてきたのは、根底にある問題が変わっていないからです。責任が曖昧になり、契約が嘘をつき、依存関係が誤った場所で固まると、ソフトウェアは今なお保守しづらくなります。


保守性、レイテンシ、そして現実の本番環境の制約のバランスをチームが取ろうとしているなら、Rywareは、システムを高速で運用しやすく、変更しやすい状態に保つ現実的なアーキテクチャの規律をもって、カスタムソフトウェア、データプラットフォーム、クラウドシステムを構築します。

プロジェクトをお考えですか?

構築したいものを教えてください。最適なアプローチを一緒に見つけます。

お問い合わせ

© 2026 - Ryware.