4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIエージェントに「設計思想」を教える技術——Cursor/Claude Codeで使える原則カタログ

4
Posted at

ねらい

CursorやClaude CodeなどのAIエージェントに設計思想を伝えることで、「動くけど散らかったコード」から「保守しやすい美しいコード」へ進化させる。この記事では、主要な設計原則とアーキテクチャパターンを体系的に整理し、すぐに使える設定ファイルのテンプレートを提供する。

対象

  • AIコーディングツールを使っているが、生成コードの品質に不満がある人
  • 「SOLID」「DRY」「クリーンアーキテクチャ」など聞いたことはあるが体系的に理解していない人
  • Cursor RulesやCLAUDE.mdに何を書けばいいか迷っている人

ゴール

この記事を読み終えると、設計思想の全体像を把握し、プロジェクトに応じて適切な原則をAIエージェントに指示できるようになる。

TL;DR

設計思想は大きく3つの階層に分かれる。

  • 設計原則: SOLID、DRY、KISS、YAGNI、関心の分離、高凝集・低結合など——コードを書く際の「心構え」
  • アーキテクチャパターン: MVC、Clean Architecture、Hexagonal Architectureなど——システム全体の「構造」
  • デザインパターン: GoFパターンなど——繰り返し現れる問題の「解法集」

これらをAIエージェントの設定ファイルに記載することで、一貫性のある高品質なコード生成が可能になる。


なぜAIエージェントに設計思想が必要なのか

AIエージェントは驚くほど賢い。でも「賢い」と「良いコードを書く」は別物だ。

新卒エンジニアを想像してほしい。優秀な大学を出て、アルゴリズムの知識は豊富。でもプロジェクトに入ったばかりの彼らに「ログイン機能作って」と言ったらどうなるか。動くものはできるかもしれないが、既存コードとの整合性は怪しいし、テストしにくい構造になっているかもしれない。

AIエージェントも同じだ。

彼らには「このプロジェクトではどういう設計方針で書くか」という文脈がない。だから教える必要がある。「うちはMVCで書いてるんだ」「このプロジェクトではSOLID原則を守ってくれ」「DRYを意識してくれ」と。

以下、AIエージェントに伝えるべき設計思想を3つの階層に分けて解説する。


第1階層:設計原則(Principles)

設計原則は、コードを書く際の「心構え」だ。具体的なコード構造を規定するものではないが、あらゆる設計判断の基盤になる。

SOLID原則

SOLIDは、オブジェクト指向設計の5つの原則の頭文字をとったもの。Robert C. Martin(通称Uncle Bob)が2000年の論文「Design Principles and Design Patterns」で提唱し、Michael FeathersがSOLIDという頭字語を作った。

SOLID is an acronym representing five fundamental object-oriented design principles formulated by Robert C. Martin, also known as Uncle Bob.

(SOLIDは、Robert C. Martin(Uncle Bob)が定式化した5つの基本的なオブジェクト指向設計原則の頭字語です)

出典: Baeldung - A Solid Guide to SOLID Principles

5つの原則を順番に見ていこう。

S - 単一責任の原則(Single Responsibility Principle)

「クラスが変更される理由は1つだけであるべき」という原則。

レストランで言えば、シェフは料理だけ、ウェイターは接客だけを担当する。シェフが接客もして経理もしていたら、どこかでミスが起きる。

# 悪い例:複数の責任が混在
class User:
    def save_to_database(self): ...
    def send_email(self): ...
    def generate_report(self): ...

# 良い例:責任を分離
class User: ...
class UserRepository:
    def save(self, user): ...
class EmailService:
    def send(self, user, message): ...
class ReportGenerator:
    def generate(self, user): ...

O - 開放閉鎖の原則(Open-Closed Principle)

「拡張に対して開き、修正に対して閉じている」べきという原則。

新機能を追加するとき、既存コードを修正するのではなく、新しいコードを追加する形で対応する。

# 悪い例:新しい割引タイプを追加するたびにif文を増やす
def calculate_discount(customer_type):
    if customer_type == "regular":
        return 0.1
    elif customer_type == "premium":
        return 0.2
    elif customer_type == "vip":  # 追加のたびに修正が必要
        return 0.3

# 良い例:新しい割引タイプはクラスを追加するだけ
class DiscountStrategy:
    def calculate(self) -> float: ...

class RegularDiscount(DiscountStrategy):
    def calculate(self): return 0.1

class PremiumDiscount(DiscountStrategy):
    def calculate(self): return 0.2

L - リスコフの置換原則(Liskov Substitution Principle)

「子クラスは親クラスの代わりに使えるべき」という原則。

Barbara Liskovが1987年に提唱した。継承を使うとき、子クラスが親クラスの「契約」を破ってはいけない。

# 悪い例:Penguinはfly()を呼ぶと例外を投げる
class Bird:
    def fly(self): ...

class Penguin(Bird):
    def fly(self):
        raise Exception("ペンギンは飛べない!")

# 良い例:飛べる鳥と飛べない鳥を分離
class Bird: ...
class FlyingBird(Bird):
    def fly(self): ...
class Penguin(Bird):  # flyメソッドなし
    def swim(self): ...

I - インターフェース分離の原則(Interface Segregation Principle)

「クライアントは使わないメソッドに依存すべきでない」という原則。

巨大なインターフェースを作るのではなく、小さく分割する。

# 悪い例:すべてのワーカーに食事と仕事を強制
class IWorker:
    def work(self): ...
    def eat(self): ...

class Robot(IWorker):
    def work(self): ...
    def eat(self):  # ロボットは食べない!
        pass

# 良い例:インターフェースを分離
class IWorkable:
    def work(self): ...

class IFeedable:
    def eat(self): ...

class Human(IWorkable, IFeedable): ...
class Robot(IWorkable): ...

D - 依存性逆転の原則(Dependency Inversion Principle)

「高レベルモジュールは低レベルモジュールに依存すべきでない。両者とも抽象に依存すべき」という原則。

# 悪い例:OrderServiceがMySQLDatabaseに直接依存
class MySQLDatabase:
    def save(self, data): ...

class OrderService:
    def __init__(self):
        self.db = MySQLDatabase()  # 具象クラスに依存

# 良い例:抽象(インターフェース)に依存
class IDatabase:
    def save(self, data): ...

class MySQLDatabase(IDatabase):
    def save(self, data): ...

class OrderService:
    def __init__(self, db: IDatabase):  # 抽象に依存
        self.db = db

DRY(Don't Repeat Yourself)

DRYは「同じ知識を2箇所以上に書くな」という原則。Andrew HuntとDavid Thomasが1999年の名著「The Pragmatic Programmer」で提唱した。

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

(すべての知識は、システム内で単一の、曖昧さのない、権威ある表現を持たなければならない)

出典: The Pragmatic Programmer(Hunt & Thomas, 1999)

注意したいのは、DRYは「コードの重複」だけでなく「知識の重複」を指すこと。同じロジックが異なる形で2箇所に存在するのもDRY違反だ。

# DRY違反:税率計算が2箇所に存在
def calculate_product_price(price):
    return price * 1.10  # 消費税10%

def calculate_service_price(price):
    return price * 1.10  # 消費税10%(同じロジック!)

# DRY適用:税率計算を一元化
TAX_RATE = 0.10

def apply_tax(price):
    return price * (1 + TAX_RATE)

def calculate_product_price(price):
    return apply_tax(price)

def calculate_service_price(price):
    return apply_tax(price)

KISS(Keep It Simple, Stupid)

KISSは「シンプルに保て」という原則。1960年代のアメリカ海軍で生まれたとされる。

Leonardo da Vinciの言葉がこの本質を捉えている。

Simplicity is the ultimate sophistication.

(シンプルさは究極の洗練である)

プログラミングでは、

  • 理解しやすいコードを書く
  • 不必要な複雑さを避ける
  • 「賢く見える」コードより「読みやすい」コードを優先する
# 複雑すぎる:ワンライナーで書けるからといって書くべきではない
result = [x for x in [y**2 for y in range(10) if y % 2 == 0] if x > 10]

# シンプル:意図が明確
even_numbers = [y for y in range(10) if y % 2 == 0]
squares = [x**2 for x in even_numbers]
result = [x for x in squares if x > 10]

YAGNI(You Aren't Gonna Need It)

YAGNIは「今必要ないものは作るな」という原則。Extreme Programming(XP)から生まれた。

Always implement things when you actually need them, never when you just foresee that you need them.

(必要になったときに実装せよ。将来必要になるかもしれないという予測で実装するな)

— Ron Jeffries(XP共同創設者)

将来の拡張性を考えて複雑な設計をするより、今必要なシンプルな実装を選ぶ。

# YAGNI違反:「将来使うかも」で複雑化
class UserService:
    def get_user(self, user_id, 
                 include_orders=False, 
                 include_reviews=False,
                 include_recommendations=False,
                 cache_strategy=None,
                 ...):  # 今は使わないオプションが山盛り

# YAGNI適用:今必要な機能だけ
class UserService:
    def get_user(self, user_id):
        return self.repository.find(user_id)

関心の分離(Separation of Concerns)

1974年、オランダの計算機科学者Edsger W. Dijkstraが論文「On the role of scientific thought」で提唱した。

It is what I sometimes have called "the separation of concerns", which, even if not perfectly possible, is yet the only available technique for effective ordering of one's thoughts, that I know of.

(私が時折「関心の分離」と呼んでいるものは、たとえ完璧には不可能であっても、思考を効果的に整理するために私が知る唯一の技法である)

出典: Dijkstra, E.W. (1974). On the role of scientific thought

システムを「関心」ごとに分割し、それぞれを独立して考えられるようにする。Webアプリケーションで言えば、「表示」「ビジネスロジック」「データアクセス」を分離するのがこれにあたる。


高凝集・低結合(High Cohesion, Low Coupling)

Larry Constantineが1960年代に開発し、1974年のIBM Systems Journal論文「Structured Design」で発表された。

Cohesion is the degree to which the internal contents of a module are related. Coupling is the degree to which a module depends upon other modules.

(凝集度はモジュール内部の要素がどれだけ関連しているかの度合い。結合度はモジュールが他のモジュールにどれだけ依存しているかの度合い)

出典: Wikipedia - Larry Constantine

高凝集(High Cohesion): モジュール内の要素が強く関連している。UserクラスにはUser関連のメソッドだけがある状態。

低結合(Low Coupling): モジュール間の依存が少ない。Userクラスを変更しても、Orderクラスに影響しない状態。

これは「変更の影響範囲を最小化する」という設計目標を達成するための原則だ。


第2階層:アーキテクチャパターン(Architecture Patterns)

設計原則が「心構え」なら、アーキテクチャパターンは「システム全体の構造」を規定する。

MVC(Model-View-Controller)

1978年、Trygve ReenskaugがXerox PARCで開発。詳細は前回の記事を参照してほしいが、要点は「データ(Model)」「表示(View)」「制御(Controller)」の分離だ。

適用場面: Webアプリケーション、GUIアプリケーション
代表的フレームワーク: Ruby on Rails, Django, Spring MVC


MVP(Model-View-Presenter)

MVCの派生形。Viewがより「受動的」になり、Presenterがすべてのロジックを担当する。

MVCとの違い: MVCではViewがModelを直接参照できるが、MVPではPresenter経由のみ。

適用場面: デスクトップアプリケーション、Android(従来)
メリット: Viewのテストが容易


MVVM(Model-View-ViewModel)

Microsoftが2005年に発表。データバインディングを活用し、ViewとViewModelを同期させる。

適用場面: WPF、Xamarin、Vue.js、Angular
メリット: 双方向データバインディングにより、UIとロジックの同期が容易


レイヤードアーキテクチャ(Layered Architecture)

最も古典的なアーキテクチャパターン。システムを水平方向のレイヤーに分割する。

┌─────────────────────┐
│  Presentation Layer │  ← UI、コントローラー
├─────────────────────┤
│  Business Layer     │  ← ビジネスロジック
├─────────────────────┤
│  Data Access Layer  │  ← リポジトリ、ORM
├─────────────────────┤
│  Database Layer     │  ← データベース
└─────────────────────┘

原則: 上位レイヤーは下位レイヤーにのみ依存する。逆方向の依存は禁止。


Hexagonal Architecture(ヘキサゴナルアーキテクチャ)

Alistair Cockburnが2005年に発表。別名「Ports and Adapters」。

The idea of Hexagonal Architecture is to put inputs and outputs at the edges of our design. Business logic should not depend on whether we expose a REST or a GraphQL API, and it should not depend on where we get data from.

(ヘキサゴナルアーキテクチャのアイデアは、入力と出力を設計の端に置くことです。ビジネスロジックは、REST APIかGraphQL APIかに依存すべきでなく、データをどこから取得するかにも依存すべきでありません)

出典: DEV Community - Hexagonal Architecture and Clean Architecture

構造:

  • 中心: アプリケーションコア(ビジネスロジック)
  • ポート: インターフェース(入力ポート/出力ポート)
  • アダプター: 具体的な実装(REST API、データベース、外部サービス)
        ┌─────────────────────────┐
        │       Adapters          │
        │  (REST, CLI, Tests)     │
        │           │             │
        │     ┌─────▼─────┐       │
        │     │   Ports   │       │
        │     │ (Interface)│       │
        │     │     │     │       │
        │     │ ┌───▼───┐ │       │
        │     │ │ Core  │ │       │
        │     │ │(Logic)│ │       │
        │     │ └───────┘ │       │
        │     └───────────┘       │
        │           │             │
        │     Adapters            │
        │  (DB, External APIs)    │
        └─────────────────────────┘

Clean Architecture(クリーンアーキテクチャ)

Robert C. Martin(Uncle Bob)が2012年に発表。Hexagonal Architecture、Onion Architecture、その他のアーキテクチャを統合したもの。

Clean Architecture, proposed by Robert C. Martin in 2012, combines the principles of the hexagonal architecture, the onion architecture and several other variants.

(2012年にRobert C. Martinが提唱したクリーンアーキテクチャは、ヘキサゴナルアーキテクチャ、オニオンアーキテクチャ、その他の派生形の原則を組み合わせたものです)

出典: Wikipedia - Hexagonal Architecture

同心円構造:

┌────────────────────────────────────┐
│       Frameworks & Drivers         │  ← 最外層: Web, DB, UI
│  ┌──────────────────────────────┐  │
│  │    Interface Adapters         │  │  ← Controllers, Gateways
│  │  ┌────────────────────────┐  │  │
│  │  │   Application Logic    │  │  │  ← Use Cases
│  │  │  ┌──────────────────┐  │  │  │
│  │  │  │     Entities     │  │  │  │  ← 最内層: ドメインモデル
│  │  │  └──────────────────┘  │  │  │
│  │  └────────────────────────┘  │  │
│  └──────────────────────────────┘  │
└────────────────────────────────────┘

依存性のルール: 依存の方向は常に「外から内へ」。内側の層は外側の層を知らない。


第3階層:デザインパターン(Design Patterns)

GoF(Gang of Four)が1994年に発表した23のパターンが有名。繰り返し現れる問題への「解法集」として機能する。

ここでは代表的なものだけ紹介する。

生成パターン(Creational)

  • Factory: オブジェクト生成をカプセル化
  • Singleton: インスタンスを1つに限定
  • Builder: 複雑なオブジェクトを段階的に構築

構造パターン(Structural)

  • Adapter: 互換性のないインターフェースを接続
  • Decorator: 機能を動的に追加
  • Facade: 複雑なサブシステムへのシンプルなインターフェース

振る舞いパターン(Behavioral)

  • Observer: 状態変化を通知
  • Strategy: アルゴリズムを交換可能に
  • Command: リクエストをオブジェクト化

AIエージェント設定ファイルのテンプレート

これらの設計思想をAIエージェントに伝えるためのテンプレートを提供する。プロジェクトの規模や性質に応じて取捨選択してほしい。

小規模プロジェクト用(シンプル版)

# .cursor/rules/design.mdc または CLAUDE.md

## 設計原則

このプロジェクトでは以下の原則を守ること。

### 必須原則
- DRY: 同じロジックを複数箇所に書かない。共通化する。
- KISS: シンプルに書く。「賢い」コードより「読みやすい」コードを優先。
- YAGNI: 今必要な機能だけを実装する。「将来使うかも」は禁止。

### コードスタイル
- 関数は1つの責任だけを持つ(SRP)
- ネストは3階層まで
- 関数は50行以内を目安

### ディレクトリ構造

src/
├── models/ # データ構造とビジネスロジック
├── views/ # 表示関連
├── controllers/ # リクエスト処理
└── utils/ # 共通ユーティリティ

中規模プロジェクト用(標準版)

設計ガイドライン

アーキテクチャ

このプロジェクトはクリーンアーキテクチャを採用。

レイヤー構成

src/
├── domain/           # ドメイン層(最内層)
│   ├── entities/     # エンティティ
│   └── repositories/ # リポジトリインターフェース
├── application/      # アプリケーション層
│   ├── usecases/     # ユースケース
│   └── services/     # アプリケーションサービス
├── infrastructure/   # インフラ層(最外層)
│   ├── database/     # DB実装
│   └── external/     # 外部API連携
└── presentation/     # プレゼンテーション層
    ├── controllers/  # コントローラー
    └── views/        # ビュー/レスポンス

依存性のルール

  • 依存の方向: presentation → application → domain
  • infrastructure → domain(インターフェース経由のみ)
  • domain層は他の層に依存しない

SOLID原則

S - 単一責任

  • 1クラス1責任
  • 「変更理由」が複数ある場合は分割

O - 開放閉鎖

  • 機能追加は新しいクラスの追加で対応
  • 既存コードの修正は最小限に

L - リスコフ置換

  • 子クラスは親クラスの契約を守る
  • 継承より合成を優先

I - インターフェース分離

  • 巨大なインターフェースは分割
  • クライアントが使わないメソッドを強制しない

D - 依存性逆転

  • 具象クラスではなくインターフェースに依存
  • DIコンテナを活用

その他の原則

DRY

  • 同じロジックは1箇所に
  • 設定値は定数化
  • 重複コードは関数化

KISS

  • シンプルな解法を選ぶ
  • 過度な抽象化を避ける
  • コメントなしでも読めるコードを目指す

高凝集・低結合

  • モジュール内は強く関連した要素のみ
  • モジュール間の依存は最小限に
  • インターフェース経由で疎結合を維持

大規模プロジェクト用(詳細版)

アーキテクチャ設計書

概要

このプロジェクトはヘキサゴナルアーキテクチャ(Ports and Adapters)を採用。
ドメイン駆動設計(DDD)の戦術的パターンと組み合わせる。

ディレクトリ構成

src/
├── core/                    # アプリケーションコア
│   ├── domain/              # ドメイン層
│   │   ├── models/          # エンティティ・値オブジェクト
│   │   ├── services/        # ドメインサービス
│   │   ├── events/          # ドメインイベント
│   │   └── repositories/    # リポジトリインターフェース
│   └── application/         # アプリケーション層
│       ├── commands/        # コマンドハンドラ
│       ├── queries/         # クエリハンドラ
│       └── ports/           # ポート定義
├── adapters/                # アダプター層
│   ├── inbound/             # インバウンドアダプター
│   │   ├── http/            # REST/GraphQL
│   │   ├── cli/             # CLI
│   │   └── events/          # イベントリスナー
│   └── outbound/            # アウトバウンドアダプター
│       ├── persistence/     # データベース
│       ├── messaging/       # メッセージング
│       └── external/        # 外部サービス
└── config/                  # 設定・DI

レイヤー別責務

ドメイン層(core/domain)

  • ビジネスルールの実装
  • エンティティと値オブジェクトの定義
  • ドメインサービス(複数エンティティにまたがるロジック)
  • 外部依存は一切持たない

アプリケーション層(core/application)

  • ユースケースのオーケストレーション
  • トランザクション境界の管理
  • ドメイン層の呼び出し
  • ポート(インターフェース)の定義

アダプター層(adapters)

  • インバウンド: 外部からのリクエストをアプリケーション層に変換
  • アウトバウンド: アプリケーション層のリクエストを外部サービスに変換
  • ポートの具体的な実装

実装パターン

エンティティ

class User:
    def __init__(self, id: UserId, email: Email, name: UserName):
        self._id = id
        self._email = email
        self._name = name
        self._events: List[DomainEvent] = []
    
    def change_email(self, new_email: Email) -> None:
        # ビジネスルールのバリデーション
        if self._email == new_email:
            raise DomainException("同じメールアドレスには変更できません")
        self._email = new_email
        self._events.append(UserEmailChanged(self._id, new_email))

ユースケース

class ChangeUserEmailUseCase:
    def __init__(self, user_repository: IUserRepository):
        self._user_repository = user_repository
    
    def execute(self, command: ChangeUserEmailCommand) -> None:
        user = self._user_repository.find_by_id(command.user_id)
        if user is None:
            raise UserNotFoundError(command.user_id)
        
        email = Email(command.new_email)  # 値オブジェクトでバリデーション
        user.change_email(email)
        self._user_repository.save(user)

ポートとアダプター

# ポート(インターフェース)
class IUserRepository:
    def find_by_id(self, user_id: UserId) -> Optional[User]: ...
    def save(self, user: User) -> None: ...

# アダプター(具体実装)
class PostgreSQLUserRepository(IUserRepository):
    def __init__(self, session: Session):
        self._session = session
    
    def find_by_id(self, user_id: UserId) -> Optional[User]:
        # PostgreSQL固有の実装
        ...

設計原則チェックリスト

新しいファイルを作成する前に

  • どのレイヤーに属するか判断したか
  • 単一責任を持つか確認したか
  • 依存の方向が正しいか確認したか

コードレビュー時

  • DRY違反がないか
  • インターフェースを通じて依存しているか
  • ドメイン層に外部依存が入っていないか
  • テスト可能な設計になっているか

設計思想の選び方

「どれを使えばいいの?」という質問への回答。

プロジェクト規模別

小規模(〜1000行)

  • DRY, KISS, YAGNIだけで十分
  • MVCくらいの軽い構造化

中規模(〜10,000行)

  • SOLID原則を追加
  • レイヤードアーキテクチャまたはMVC

大規模(10,000行〜)

  • Clean ArchitectureまたはHexagonal Architecture
  • DDDの戦術的パターン
  • すべての原則を意識

チーム規模別

1人: シンプルさ優先。YAGNIを徹底。
2-5人: 一貫性のためにアーキテクチャパターンを導入。
5人以上: 明確な層分離と依存性のルールが必須。


まとめ

設計思想は「制約」ではなく「武器」だ。

AIエージェントに設計思想を伝えることで、

  • コードの一貫性が保たれる
  • 保守性が向上する
  • チーム全体の生産性が上がる

ただし、すべての原則を最初から完璧に守る必要はない。プロジェクトの規模と複雑さに応じて、段階的に取り入れていくのがコツだ。

まずは「DRY、KISS、YAGNI」の3原則から始めて、必要に応じてSOLIDやアーキテクチャパターンを追加していく。そんなアプローチを勧める。

最後に、Dijkstraの言葉を引用しよう。

The separation of concerns, even if not perfectly possible, is yet the only available technique for effective ordering of one's thoughts.

完璧でなくても、思考を整理する唯一の方法——それが設計思想なのだ。


参考リンク

設計原則

アーキテクチャパターン

AIエージェント設定

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?