solid について
オブジェクト指向コードにおける SOLID の五原則を解説するソフトウェア設計スキルです。保守性や拡張性の問題を診断し、的を絞った設計変更を検討できます。
主な機能
- 01単一責任の原則に関する指針
- 02開放閉鎖の原則による拡張
- 03リスコフの置換原則の確認
- 04インターフェース分離の指針
- 05依存性逆転の原則に関する指針
- 06原則間の関係とよくある誤解
活用シーン
- 01責務が多すぎるクラスの診断
- 02既存動作を拡張する機能の設計
- 03継承関係とインターフェース境界の確認
- 04具体的な実装詳細への依存の削減
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ krzysztofsurdy/ code-virtuoso --skill solidインストール後、Claude Code で/solidを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/dip.mdisp.mdlsp.mdocp.mdsrp.md詳細
SOLID 原則
システムの保守、テスト、拡張を容易にする、オブジェクト指向設計の5つの基本原則です。Robert C. Martin(Uncle Bob)がこれらの考え方を体系化し、Michael FeathersがSOLIDという頭字語を考案しました。
原則一覧
| 原則 | 概要 | 参照 |
|---|---|---|
| S: 単一責任 | クラスが変更される理由は1つだけにする | reference |
| O: オープン/クローズド | 拡張に対して開き、変更に対して閉じる | reference |
| L: リスコフの置換 | サブタイプは基底型と置き換え可能でなければならない | reference |
| I: インターフェース分離 | 汎用インターフェースを1つ設けるより、用途に特化したインターフェースを複数設ける | reference |
| D: 依存関係逆転 | 具象ではなく抽象に依存する | reference |
SOLIDが重要な理由
SOLIDに従わないと、コードベースには次のような問題が徐々に生じます。
- 硬直性:1つの変更が、無関係な多くのモジュールに波及します
- 脆弱性:ある領域を変更すると、一見関係のない別の領域が壊れます
- 再利用困難性:コンポーネントが密接に絡み合い、再利用のために切り出すのが現実的ではありません
- 粘着性:設計に沿って正しく作業するより、設計を回避する場当たり的な対応のほうが簡単になります
SOLIDは、明確な境界、明示的な契約、拡張のための定義済みポイントによって、それぞれの問題に対処します。
簡易判断ガイド
| 症状 | 違反している可能性がある原則 | 対処法 |
|---|---|---|
| クラスが多くのことを行っている | SRP | 役割を絞ったクラスに分割する |
| 機能の追加に既存クラスの編集が必要 | OCP | ポリモーフィズムまたはストラテジーを導入する |
| 親クラスの代わりに使うとサブクラスが壊れる | LSP | 継承階層を修正するか、コンポジションを使う |
| クラスが使わないメソッドの実装を強いられる | ISP | インターフェースを小さく分割する |
| 高水準モジュールが低水準の詳細をインポートしている | DIP | 抽象化レイヤーを導入する |
簡単な例
SRP:変更前と変更後
// BEFORE: Class handles both user data AND email sending
class UserService {
public function createUser(string $name, string $email): void { /* ... */ }
public function sendWelcomeEmail(string $email): void { /* ... */ }
}
// AFTER: Each class has one responsibility
class UserService {
public function __construct(private UserNotifier $notifier) {}
public function createUser(string $name, string $email): void {
// persist user...
$this->notifier->welcomeNewUser($email);
}
}
class UserNotifier {
public function welcomeNewUser(string $email): void { /* ... */ }
}
OCP:変更せずに拡張する
interface DiscountPolicy {
public function calculate(float $total): float;
}
class PercentageDiscount implements DiscountPolicy {
public function __construct(private float $rate) {}
public function calculate(float $total): float {
return $total * $this->rate;
}
}
// Adding a new discount type requires NO changes to existing code
class FlatDiscount implements DiscountPolicy {
public function __construct(private float $amount) {}
public function calculate(float $total): float {
return min($this->amount, $total);
}
}
DIP:抽象に依存する
// High-level policy depends on abstraction, not on database details
interface OrderRepository {
public function save(Order $order): void;
}
class PlaceOrderHandler {
public function __construct(private OrderRepository $repository) {}
public function handle(PlaceOrderCommand $cmd): void {
$order = Order::create($cmd->items);
$this->repository->save($order);
}
}
原則間の関係
5つの原則は互いに補強し合います。
- SRP + ISP:どちらも、クラスやインターフェースの責務を1つに絞ります
- OCP + DIP:抽象化によって、変更せずに拡張できるようになります
- LSP + OCP:ポリモーフィズムによる拡張が機能するのは、サブタイプを正しく置き換えられる場合に限られます
- ISP + DIP:適切に分離されたインターフェースにより、必要な抽象にだけ依存しやすくなります
よくある誤解
- 「クラスごとにメソッドは1つ」:SRPが意味するのは「変更する理由を1つにする」ことであり、メソッドを1つだけにすることではありません。同じ責務に関わるメソッドであれば、クラスに複数あってもかまいません。
- 「既存コードは絶対に変更しない」:OCPはバグ修正を禁止するものではありません。「新しい振る舞い」を、既存の正常に動作するコードを変更せずに追加できるようにするという意味です。
- 「常にインターフェースを使う」:DIPが求めるのは抽象への依存です。基底クラスが適切な抽象となる場合もあります。2つ目の実装が決して作られないクラスに、インターフェースを作る必要はありません。
- 「継承は悪いもの」:LSPは継承を避けるよう求めるものではありません。サブタイプを置き換え可能な状態に保つために、継承を「正しく」行うルールを定めています。
ベストプラクティス
- SOLIDは段階的に取り入れましょう。一度にすべてをリファクタリングしないでください
- SOLIDを診断ツールとして使いましょう。コードの変更が難しいときは、どの原則に違反しているか確認してください
- デザインパターンと組み合わせましょう。Strategy(OCP)、Adapter(DIP)、Decorator(OCP)は、SOLIDの考え方を直接実装したものです
- まずテストを書きましょう。TDDはSOLIDに準拠した設計を促します
- 言語の厳密な型付けや不変性の機能(readonlyまたはfinalフィールド、不変の値型、列挙型、sealed型)を使い、コンパイラーによって設計を強制しましょう
コメント
Markdown 対応