solid について
solid は、5 つの SOLID 設計原則を解説し、実践方法とコードレビューの観点を示す開発スキルです。モジュール性とテスト容易性を重視する開発者向けで、抽象化が不要な場合についても説明します。
主な機能
- 015 つの SOLID 原則の解説
- 02よくある設計違反の例
- 03実践に向けた適用方法
- 04コードレビューのチェックリスト
- 05過度な抽象化への注意
活用シーン
- 01モジュールやコンポーネントの責務をレビュー
- 02肥大化したインターフェースや分岐のリファクタリング
- 03テスト容易性を踏まえた依存境界の評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jordancoin/ codingskills --skill solidインストール後、Claude Code で/solidを入力して読み込まれたか確認します。
詳細
SOLID 原則
適用する前に
.agents/stack-context.md が存在する場合は、まず読みます。検出されたスタックに適した慣用的なパターンを使って、この原則を適用してください。フレームワーク固有の詳細については、推測せずに context7 MCP またはウェブ検索を利用してください。
原則
モジュール性、テスト容易性、回復力のあるシステムを実現する、5 つの設計原則です。もともとはオブジェクト指向設計を前提にしていますが、その根底にある考え方は、関数、モジュール、サービス、コンポーネントなど、あらゆるパラダイムに適用できます。
本番環境で重要な理由
SOLID に沿ったコードは、変更に強いコードです。本番システムは、新機能、バグ修正、連携、スケーリングなど、絶えず変化します。SOLID 原則に基づいて設計されたシステムは、変更の影響を局所的にとどめ、連鎖的な修正を必要としません。その結果、リグレッションが減り、機能をより速く提供でき、問題が起きた際の影響範囲も小さくなります。
S — 単一責任の原則
モジュールが変更される理由は、ただ 1 つであるべきです。
各モジュール、関数、コンポーネントは、機能を 1 つだけ担うべきです。変更依頼が来たとき、探し回らなくても編集が必要なファイルを予測できるようにします。
違反の例:
- 入力を検証し、データベースに問い合わせ、ビジネスロジックを適用し、レスポンスを整形する関数
- 明確な置き場所がないため、際限なく肥大化する「utils」ファイル
- システムのどの機能が変わっても変更されるクラス
適用方法:
- 関数が 2 つのことを行っている場合は、2 つの関数に分割します
- モジュールが「何をするか」と「どう実現するか」の両方を担っている場合は、方針と仕組みを分離します
- モジュールには、その責任を表す名前を付けます。「Manager」「Helper」「Utils」のように曖昧な名前であれば、責任も曖昧です
O — 開放/閉鎖の原則
ソフトウェアは拡張に対して開かれ、変更に対して閉じられているべきです。
既存の動作するコードを変更せずに、新しい振る舞いを追加できるようにします。これは、既存のコードに一切手を加えないという意味ではありません。一般的な拡張ポイントで変更が不要になるように設計するということです。
違反の例:
- 機能を追加するたびに新しいケースが増えていく switch 文
- 対象となる型が増え続けるなか、
if type == "x"を確認する関数 - 新しい連携を追加するたびに、中央のディスパッチ関数を変更する必要がある
適用方法:
- 型による分岐の代わりに、ディスパッチテーブル、レジストリ、ポリモーフィズムを使います
- 新しい実装が満たせる契約(インターフェース、プロトコル、関数シグネチャ)を中心に設計します
- プラグインアーキテクチャを採用します。ただし、複数のプラグインがある場合に限ります(YAGNI を参照)
L — リスコフの置換原則
サブタイプは、意外な振る舞いをすることなく、親型が想定されるあらゆる場面で使用できなければなりません。
コードが基底型を受け取る場合、その型のどの実装でも正しく動作するべきです。特別扱いや、「型 X の場合を除く」といったガードは不要です。
違反の例:
- 継承したメソッドに対して「未実装」をスローするサブクラス
- 汎用的な型を受け取った後で振る舞いを決めるための型チェック(
instanceof、typeof) - 使用する側が、どの具体的な実装を扱っているか把握しなければならない
適用方法:
- サブタイプが親型の契約全体を満たせない場合は、サブタイプにせず、代わりにコンポジションを使います
- 実装が提供できるものではなく、使用する側が必要とするものを中心にインターフェースを設計します
- 置換してテストします。実装を入れ替えても、システムが正しく動作することを確認します
I — インターフェース分離の原則
利用者に、使用しないメソッドへの依存を強制してはいけません。
インターフェースは小さく、目的を絞って設計します。データを読み取るだけの利用者が、書き込み、削除、管理操作も含むインターフェースに依存しないようにします。
違反の例:
- 多くの実装が一部しか満たしていない、20 個以上のメソッドを持つ「万能インターフェース」
- 大きなモジュールをインポートしているのに、関数を 1 つしか使わない利用者
- テストでは 2 つのメソッドしか実行しないのに、15 個のメソッドをスタブ化する必要があるモックオブジェクト
適用方法:
- 大きなインターフェースを役割ごとに分割します。
Repositoryではなく、Reader、Writer、Adminにします - インターフェースのない言語では、この原則をモジュールの境界に適用し、利用者が必要とするものだけをエクスポートします
- テスト用のモックを書くのが大変なら、インターフェースの範囲が広すぎます
D — 依存関係逆転の原則
高レベルの方針は、低レベルの詳細に依存してはいけません。どちらも抽象に依存するべきです。
ビジネスロジックから、データベースドライバー、HTTP クライアント、ファイルシステム操作を直接インポートしてはいけません。こうした詳細を実装する抽象に依存させます。
違反の例:
- データベース接続、HTTP クライアント、外部 SDK を直接インスタンス化するビジネスロジック
- 実際のインフラを起動しないと、ビジネスルールをテストできない
- データベースや API のプロバイダーを変更するたびに、ビジネスロジックを書き直す必要がある
適用方法:
- 依存関係を内部で構築するのではなく、外部から渡します(依存性注入)
- ビジネスロジックの境界にインターフェースを定義し、インフラ側でそれに適合させます
- 関数型コードでは、特定の実装への呼び出しをハードコードするのではなく、関数をパラメーターとして受け取ります
境界
- SOLID はヒューリスティックであり、法則ではありません。 小さなスクリプト、プロトタイプ、つなぎ込み用のコードに、SOLID をすべて適用する必要はありません。変更コストを下げられる箇所に原則を適用します。
- 抽象化しすぎないようにします。 SOLID を極端に適用すると、小さなクラスやインターフェースが増えすぎ、元のモノリスより把握しにくくなります。抽象化は、複数の利用者に役立つか、意味のあるテスト容易性を実現することで、その価値を示す必要があります。
- KISS との緊張関係: インターフェース、注入ポイント、抽象化を追加するたびに、間接層が増えます。予防的に適用するのではなく、システムが実際にその側面で変化する箇所に SOLID を適用します。
- 関数型コードでは、一部の領域で SOLID を自然に満たせます。 純粋関数は自然に SRP を満たします。高階関数は自然に DIP を満たします。SOLID の適用は、特に重要なアーキテクチャの境界に重点を置きます。
コードレビューのチェックリスト
- 各モジュールの責務は明確で、単一ですか?
- 既存の動作するコードを変更せずに、新しい動作を追加できますか?
- インターフェースは、利用者が使わないものに依存しない程度に小さくなっていますか?
- 依存関係はハードコードせず、注入されていますか?
- ビジネスロジックに触れずに、実装(データベース、API クライアントなど)を差し替えられますか?
関連スキル
- separation-of-concerns:レイヤー単位の構造について(SoC が「何を」、SOLID が「どのように」を扱います)
- law-of-demeter:コンポーネント間の結合を減らすため
- yagni:早すぎる抽象化による SOLID の過剰適用を避けるため