はじめに:AIエディタに「丸投げ」していませんか?
CursorやWindsurfなどのAIエディタの進化により、コードを書かずに自然言語だけで開発を進めるスタイルが普及してきました。
しかし、こんな経験はありませんか?
- 「〇〇の機能を作って」と雑に頼んだら、既存のコードの別機能まで破壊された
- コンポーネントが肥大化し、保守不可能な1000行のスパゲッティコードが生まれた (もはやAIがただの「スパゲティ🍝コード製麺機」と化している状態)
- 結局、AIの書いたコードを修正するのに自分で書く以上の時間がかかった
AIは優秀ですが、人間側の「指示(要件定義)」が甘いと、平気で破綻したアーキテクチャを提案してきます。
本記事では、AIにいきなりコードを書かせず、まずは「設計」をさせるための『魔法のプロンプト』を公開します。
私は普段 Next.js / TypeScript で開発を行っていますが、この手法は Python・Go・Ruby など、どんな環境でも汎用的に使えます。
検証:いきなり書かせる vs 設計させる
簡単な「タスク管理のカンバンボード(Trello風)を作って」という指示で、実際のAIの挙動と出力されるコードを比較してみます。
❌ 失敗パターン:いきなり書かせる
▼ 入力プロンプト
「Trelloのようなカンバンボードを作ってください。タスクの追加、移動ができるようにしてください。」
結果:保守性ゼロの巨大ファイルの誕生
AIは即座にコードを書き始めますが、状態管理、ドラッグ&ドロップのロジック、UIの描画がすべて KanbanBoard.tsx という1つのファイルに詰め込まれたコードが出力されます。
// ❌ AIがいきなり出力した保守性の低いコード(抜粋)
import React, { useState } from 'react';
interface Task {
id: string;
title: string;
status: string;
}
export default function KanbanBoard() {
// 状態管理がすべてここに集中(肥大化の予兆)
const [tasks, setTasks] = useState<Task[]>([]);
const [columns, setColumns] = useState(['Todo', 'In Progress', 'Done']);
// D&Dのロジックも直書きされている
const handleDragStart = (e: React.DragEvent, taskId: string) => {
// 本来はここに複雑な状態計算やAPI通信が入る
e.dataTransfer.setData('taskId', taskId);
};
const handleDrop = (e: React.DragEvent, columnId: string) => {
// ドロップ時のタスク移動・並び替え処理(実際は50行以上の規模になる)
e.preventDefault();
};
return (
<div className="flex gap-4 p-4">
{/* 列の描画ループの中に、さらにカードの描画がネストされている */}
{columns.map(column => (
// ❌ onDragOver の preventDefault() も欠落しており、
// 実際にはドラッグ&ドロップすら動作しないバグを含む
<div key={column} onDrop={(e) => handleDrop(e, column)}>
<h2>{column}</h2>
{tasks.filter(t => t.status === column).map(task => (
<div key={task.id} draggable onDragStart={(e) => handleDragStart(e, task.id)}>
{task.title}
</div>
))}
</div>
))}
</div>
);
}
コンポーネント分割の概念がなく、後から機能追加(例:タスクの編集モーダルなど)をしようとすると確実に破綻します。
⭕️ 成功パターン:魔法の『設計プロンプト』を使う
▼ 入力プロンプト(※テンプレは後述します)
「カンバンボードを作ります。【設計プロンプト】 に従って、まずはアーキテクチャとコンポーネント設計を提示してください。私が許可するまでコードは書かないでください。」
結果:シニアエンジニア並みの要件定義を提案してくる
AIは一切コードを書かず、以下のような「設計書」をMarkdownで出力してきます。
### 1. 要件の整理
- タスクの作成、編集、削除
- ステータス(Todo, In Progress, Done)間のドラッグ&ドロップ移動
### 2. コンポーネント設計
- `KanbanBoard`: 全体のレイアウトと状態管理の親コンポーネント
- `KanbanColumn`: 各ステータスの列。ドロップゾーンとして機能
- `KanbanCard`: 個別のタスク要素。ドラッグ可能要素
### 3. データ設計・状態管理
グローバルな状態管理が必要になるため、Zustandの利用を推奨します。よろしいでしょうか?
### 4. 懸念点とエッジケース
- タスクのドラッグ中に別ユーザーが同タスクを削除・更新した場合の競合処理
- 大量タスク(100件以上)でのレンダリングパフォーマンス(仮想スクロールの導入検討)
人間は、この提案を見て「ZustandでOK。ただColumnはもう少し細かく分けて」とレビューするだけです。
設計に合意してから実装を指示すると、先ほどのスパゲッティコードとは打って変わり、機能ごとに美しく分割されたコード群が生成されます。
▼ 生成されたディレクトリ構成
src/
├── components/
│ └── kanban/
│ ├── KanbanBoard.tsx # 全体の枠組みとProvider
│ ├── KanbanColumn.tsx # 列(ドロップゾーン)
│ └── KanbanCard.tsx # タスク(ドラッグ要素)
├── store/
│ └── useKanbanStore.ts # Zustandによる状態管理
└── types/
└── kanban.ts # Task, Columnの型定義
▼ 生成されたコンポーネント例1:KanbanCard.tsx(ドラッグ要素)
// ✅ 単一責任の美しいコンポーネント
import React from 'react';
import { Task } from '@/types/kanban';
interface KanbanCardProps {
task: Task;
onDragStart: (taskId: string) => void;
}
export const KanbanCard = ({ task, onDragStart }: KanbanCardProps) => (
<div
draggable
onDragStart={() => onDragStart(task.id)}
className="p-3 mb-2 bg-white rounded shadow cursor-grab hover:bg-gray-50"
>
<p className="text-sm font-medium text-gray-800">{task.title}</p>
</div>
);
▼ 生成されたコンポーネント例2:KanbanColumn.tsx(ドロップゾーン)
// ✅ onDragOver + preventDefault() も適切に実装されている
import React from 'react';
import { Column } from '@/types/kanban';
interface KanbanColumnProps {
column: Column;
children: React.ReactNode;
onDrop: (e: React.DragEvent, columnId: string) => void;
}
export const KanbanColumn = ({ column, children, onDrop }: KanbanColumnProps) => (
<div
onDragOver={(e) => e.preventDefault()} // ← Beforeで欠落していた必須処理
onDrop={(e) => onDrop(e, column.id)}
className="flex flex-col gap-2 p-3 bg-gray-100 rounded min-h-[200px] w-64"
>
<h2 className="font-bold text-gray-700">{column.title}</h2>
{children}
</div>
);
Propsと責務が明確になり、これなら後から「タグ表示を追加したい」「期限日の警告を出したい」といった改修も安全かつ容易に行えます。
🎁 魔法の『設計プロンプト』テンプレート(コピペ用)
AIエディタのチャット欄(または .cursorrules 等のグローバルルール)に、以下のプロンプトを貼り付けてみてください。
あなたは優秀なシニアアーキテクトです。
これから新しい機能(またはアプリ)の実装を依頼しますが、**絶対にいきなりコードを書き始めないでください。**
まずは以下のステップに従って、実装計画とアーキテクチャ設計を提示してください。
1. **要件の整理:** 私の依頼内容から、実装すべき機能のリストを箇条書きでまとめてください。不足している情報があれば質問してください。
2. **コンポーネント設計:** UIをどのように分割するか、それぞれの役割(PropsとState)を定義してください。
3. **データ設計・状態管理:** データをどのように管理するかを提案してください。
4. **懸念点とエッジケース:** この実装における技術的な懸念点、パフォーマンスのボトルネック、エラーハンドリングが必要な箇所を洗い出してください。
上記の1〜4をMarkdownで提示し、私(ユーザー)の承認を得てください。
私が「設計に合意します。実装を開始してください」と指示するまで、実際のコード生成は行わないでください。
なぜここまで品質が変わるのか?(技術的背景)
これはLLMの「確率的なトークン予測」という性質を逆手に取った、Task Decomposition(タスク分割) というプロンプトエンジニアリングの基本手法です。
いきなり「コードを書いて」と指示すると、LLMは「要件の推測」「設計」「構文の記述」という複雑なタスクを同時に処理しなければならず、結果として最も確率的に無難な(=とりあえず1ファイルに全部書く)出力を選びがちです。
しかし、プロンプトで 「思考のステップを分け、先に設計書を出力させる」 プロセスを挟むことで、劇的な変化が起きます。
| アプローチ | コンテキストの状態 | コード品質 |
|---|---|---|
| いきなり実装 | ユーザーの短いプロンプトしかない | 保守できない負債 |
| 設計プロンプト | LLM自身の設計書がコンテキストに含まれる | 堅牢なコード |
LLMは「直前の文脈(コンテキスト)」に強く引っ張られます。自ら出力した理路整然とした設計書を読み込ませてからコーディングに移行させることで、シニアエンジニアが書いたような品質を担保できるのです。
【実話】この記事を書きながら、AIの「本質的な限界」を体験した
余談ですが、私自身、この記事の原稿をAIと一緒に作成している最中に 「AIが『Markdownの表を修正しました』と自信満々に報告してきたのに、プレビューで見ると完全に壊れたままだった(しかも5回連続で)」 というインシデントに遭遇しました。
▼ 実際にAIが「修正しました」と言って5回連続で出力してきたテキスト
アプローチ
コンテキストの状態
コード品質
いきなり実装
ユーザーの短いプロンプトしかない
保守できない負債
設計プロンプト
LLM自身の設計書がコンテキストに含まれる
堅牢なコード
(※パイプ文字 | と区切り行 |---| が完全に欠落しており、表として描画されない)
結局、この表のMarkdownだけは私が直接手打ちで修正する羽目になりました。
もし人間(私)の厳しいレビュー確認がなければ、この欠陥品がそのまま公開されていたわけです。AIは優秀ですが、人間側の統制が甘いと平気で破綻したものを納品してきます。
この実体験こそが、本記事で主張したい「人間が設計とレビューに集中し、AIを壁打ち相手にする」というテーマの最大の証明になりました。
⚠️ この手法の「限界」と「注意点」
万能に見えるこの手法にも、AIの特性ゆえの限界があります。実務で使う際は以下の点に注意してください。
1.小規模な修正には「過剰」
数行のバグ修正や、1ファイルで完結する単純な処理に対して設計から考えさせると、かえって時間がかかります。状況に応じて「いきなり書かせる」との使い分けが必要です。
目安として、以下のいずれかに該当する場合に使うのがおすすめです:
- 新規コンポーネントが3つ以上生まれる実装
- 状態管理(グローバルステートなど)の設計判断が必要な場合
- 既存コードへの影響範囲が読めない機能追加
2.長すぎる会話(コンテキスト汚染)に注意
設計の議論が長引きすぎると、コンテキストウィンドウが不要な情報で溢れ、いざ実装フェーズに入った時にAIが混乱することがあります。設計がまとまったら、一度チャットをリセットして「確定した設計書」だけを渡し直すと精度が安定します。
3. 推論特化モデルとの相性
OpenAIの o3/o4系 や、Claude 3.5 / 3.7 Sonnet 以降のような「内部で長考(推論)するモデル」は、自律的にタスク分割を行う能力が高いため、このプロンプトの指示が冗長になる場合があります。
まとめ:AIを「作業者」ではなく「壁打ち相手」にする
AI駆動開発において、AIへの丸投げは技術的負債の先送りにすぎません。
人間が要件定義とアーキテクチャの意思決定に集中し、AIを優秀な「壁打ち相手」として活用することこそが、真の生産性向上に繋がります。
この『設計プロンプト』を使うだけで、明日からのAIエディタの挙動が劇的に変わります。ぜひ、次の開発からコピペして試してみてください!
最後まで読んでいただき、ありがとうございました。
この記事が少しでも「役に立った!」「試してみたい」と思ったら、ぜひ LGTM やストックをお願いします! 皆さんのプロンプトの工夫などもコメント欄で教えていただけると嬉しいです。
この記事を書いた人✏️@YushiYamamoto
株式会社プロドウガ CEO / AIアーキテクト
Next.js / TypeScript / n8nを活用した自律型アーキテクチャ設計を専門としています。
日々の自動化の検証結果や、ビジネス側の視点(ROI等)に関するより深い考察は、以下の公式サイトおよびnoteで発信しています。
