design-patterns について
design-patternsは、Rust、TypeScript/React、Django/Pythonの設計パターンを選ぶためのスキルです。状況に応じた選択肢とトレードオフを比較し、アンチパターンも解説します。
主な機能
- 01Rust、TypeScript/React、Django/Pythonのパターン解説
- 02パターンの適用条件と避ける条件
- 03選択肢とトレードオフの比較
- 04アンチパターン一覧と選定手順
活用シーン
- 01特定の設計課題に合うパターンを選択
- 02プロジェクトの制約に応じて実装案を比較
- 03よくあるアンチパターンをコードで確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ waybarrios/ opencode-power-pack --skill design-patternsインストール後、Claude Code で/design-patternsを入力して読み込まれたか確認します。
詳細
デザインパターン
一般的な技術スタックに合わせたパターンの推奨です。それぞれのパターンの推奨には、使用する場面、避ける場面、トレードオフが含まれます。
クイックスタート
パターンを選ぶ
- 問題を特定する: どのような設計上の課題を解決しようとしていますか?
- コンテキストを考慮する: 使用する言語は何ですか? 規模はどのくらいですか? どのくらいの頻度で変更されますか?
- 選択肢を評価する: トレードオフを含め、候補となるパターンを2~3個挙げます
- 推奨する: この特定のコンテキストに最適なもの(「一般的に最良のパターン」ではありません)
黄金律: パターンはツールであり、目標ではありません。問題を解決できる最もシンプルなパターンを使ってください。合うパターンがなければ、通常の関数や構造体で問題ありません。
Rust パターン
Newtype パターン
プリミティブ型をラップして、型安全性とドメイン上の意味を加えます。
struct UserId(u64);
struct OrderId(u64);
// Now the compiler prevents mixing up UserId and OrderId
fn get_order(user: UserId, order: OrderId) -> Order { ... }
使用する場面: 基底の型は同じでも、意味が異なる2つの値がある場合。 使わない場面: 型エイリアスで十分な単発の用途。
Builder パターン
複雑なオブジェクトを段階的に構築します。
let config = ServerConfig::builder()
.host("localhost")
.port(8080)
.max_connections(100)
.build()?;
使用する場面: 多数のオプションフィールドや複雑な構築ロジックがある構造体。
使わない場面: 必須フィールドが1~3個の構造体。new() を使ってください。
Typestate パターン
無効な状態を表現できないように、型システムで状態遷移をエンコードします。
struct Connection<S: State> { /* ... */ state: PhantomData<S> }
struct Disconnected;
struct Connected;
struct Authenticated;
impl Connection<Disconnected> {
fn connect(self) -> Result<Connection<Connected>> { ... }
}
impl Connection<Connected> {
fn authenticate(self, creds: &Credentials) -> Result<Connection<Authenticated>> { ... }
}
impl Connection<Authenticated> {
fn query(&self, sql: &str) -> Result<Rows> { ... }
}
// Can't call query() on a Disconnected connection — compile error
使用する場面: 状態遷移が明確なプロトコル(接続、ワークフロー、解析段階)。 使わない場面: 単純なオン/オフ状態。ブール値や列挙型のほうが明確です。
トレイトオブジェクトとジェネリクスの比較
| アプローチ | ディスパッチ | バイナリサイズ | 柔軟性 |
|---|---|---|---|
impl Trait(ジェネリクス) | 静的(単相化) | 大きい(型ごとにコピーを1つ作成) | コンパイル時に判明 |
dyn Trait(トレイトオブジェクト) | 動的(vtable) | 小さい(コピーは1つ) | 実行時に拡張可能 |
ジェネリクスを使う場面: パフォーマンスが重要な場合、型がコンパイル時に判明している場合、ゼロコスト抽象化を実現したい場合。 トレイトオブジェクトを使う場面: 異種コレクションやプラグインシステムが必要な場合、または型の集合が未確定な場合。
エラーハンドリングのパターン
| パターン | 場面 | クレート |
|---|---|---|
thiserror | ライブラリのエラー — 構造化された具体的なバリアント | thiserror |
anyhow | アプリケーションのエラー — コンテキストが豊富で、すばやく伝播 | anyhow |
| カスタム列挙型 | 呼び出し側で網羅的なマッチングが必要な場合 | std のみ |
経験則: ライブラリでは thiserror を使います(呼び出し側でマッチングする必要があります)。アプリケーションでは anyhow を使います(呼び出し側にコンテキストが必要です)。
TypeScript / React パターン
複合コンポーネント
共有コンテキストを介して暗黙的に連携するコンポーネントです。
<Select>
<Select.Trigger>Choose a fruit</Select.Trigger>
<Select.Options>
<Select.Option value="apple">Apple</Select.Option>
<Select.Option value="banana">Banana</Select.Option>
</Select.Options>
</Select>
使用する場面: 複数の関連するパーツで構成される複雑な UI コンポーネント(メニュー、アコーディオン、タブ)。 使わない場面: 要素が1~2個のシンプルなコンポーネント。
カスタムフックの合成
動作を再利用可能なフックとして抽出し、組み合わせます。
function useDebounce<T>(value: T, delay: number): T { ... }
function usePagination(items: Item[], pageSize: number) { ... }
function useLocalStorage<T>(key: string, initial: T) { ... }
使用する場面: コンポーネント間で共有する状態を持つロジックや、コンポーネント内の複雑な状態管理。 使わない場面: 繰り返し使わない単発のロジック。
判別可能なユニオン
TypeScript のユニオン型を使って、型安全に状態をモデル化します。
type AsyncState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };
// Exhaustive switch — TypeScript ensures all cases handled
function render(state: AsyncState<User>) {
switch (state.status) {
case "idle": return <Placeholder />;
case "loading": return <Spinner />;
case "success": return <UserCard user={state.data} />;
case "error": return <ErrorBanner error={state.error} />;
}
}
使用する場面: 状態マシン、API レスポンスの状態、フォームの状態など、明確に区別されるモードがあるもの。 使わない場面: 単純なオン/オフ状態なら、ブール値で十分です。
状態管理の判断フロー
- UI 内だけで使う状態ですか? ->
useState/useReducer - 兄弟コンポーネント間で共有しますか? -> 共通の親に状態を引き上げる
- 離れたコンポーネント間で共有しますか? -> React Context
- 状態ロジックが複雑ですか? ->
useReducer+ Context - サーバーの状態(API データ)ですか? -> TanStack Query / SWR
- アプリ全体の状態ですか? -> Zustand / Jotai(新規プロジェクトでは Redux よりこちらを推奨)
Django / Python パターン
サービスレイヤー
ビジネスロジックをビューやモデルから分離します。
# services/order_service.py
class OrderService:
@staticmethod
def create_order(user: User, items: list[CartItem]) -> Order:
"""Business logic lives here, not in the view."""
order = Order.objects.create(user=user, total=calculate_total(items))
OrderItem.objects.bulk_create([...])
send_confirmation_email(user, order)
return order
使用する場面: 複数のモデルや外部呼び出し、複雑な検証が関わるビジネスロジック。 使わない場面: Django 組み込みの検証以外にビジネスルールがないシンプルな CRUD。
リポジトリパターン(QuerySet の抽象化)
クエリロジックをカプセル化するカスタムマネージャーとクエリセットです。
class PublishedManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status="published")
class Article(models.Model):
objects = models.Manager() # Default
published = PublishedManager() # Article.published.all()
使用する場合: 複雑または再利用可能なクエリロジック、ドメイン固有のフィルタリング。 使用を避ける場合: 一度限りのクエリや単純なフィルター。
シグナルと明示的な呼び出し
| アプローチ | 長所 | 短所 |
|---|---|---|
| Django シグナル | 疎結合で、アプリ間で機能する | 制御フローが見えにくく、デバッグが難しい。順序に関する問題がある |
| 明示的な呼び出し | 可視性があり、デバッグやテストが容易 | モジュール間の結合 |
目安: 監査ログやキャッシュの無効化など、本当に横断的な関心事にはシグナルを使用します。フローを把握してテストする必要がある、ビジネス上重要なロジックには明示的な呼び出しを使用します。
アンチパターン(すべてのスタック)
| アンチパターン | 問題 | 対処法 |
|---|---|---|
| 早すぎる抽象化 | パターンが繰り返されるのを確認する前に抽象化する | 抽出する前に3つの事例が現れるまで待つ |
| コンポジションより継承を優先 | 深い継承階層や、壊れやすい基底クラス | コンポジション(トレイト、フック、ミックスイン)を優先する |
| 神オブジェクト | あらゆる処理を行う1つのクラス/モジュール | 責任ごとに分割する(SRP) |
| ショットガン手術 | 1つの変更に多くのファイルの編集が必要になる | 関連するコードをまとめる |
| 機能への羨望 | 関数が、自身のデータよりも別のクラスのデータを多く使う | その関数を、羨望の対象となっているクラスに移す |
| 過剰な汎用化 | 必要になるか分からないユースケースに備えて構築する | YAGNI — まず現時点のニーズに合わせて構築し、必要になったらリファクタリングする |
| 金づち | あらゆることに同じパターン/ツールを使う | 目的に合ったツールを選ぶ |
パターン選択ガイド
「どのパターンを使うべきですか?」と尋ねられた場合:
- 問題を説明する(適していると思うパターンではなく)
- 制約を特定する(パフォーマンス、拡張性、チームの規模、スケジュール)
- トレードオフとともに2〜3個の選択肢を提示する
- 明確な根拠を示して1つを推奨する
- 見直すタイミングを示す — 「X が変わったら、Y を再検討する」
統合
構造/アーキテクチャの評価には、このパックの code-architect スキルを、パターンに向けたリファクタリングの動機となる複雑さのしきい値については code-quality を相互参照してください。