AIがコードを書く時代になり、現在の開発現場ではAIを活用したAI駆動開発が当たり前になっています。
AIエージェントに「この機能を追加して」と依頼するだけで、数分後には実装、動作確認までが完了している...ということが叶ってしまいます。凄いですね!
新卒エンジニアにとってのAI駆動開発
AIエージェントを利用した開発はメリットしかないように思えますが、新卒エンジニアが利用することに対しては、いくつかの問題点が浮き彫りになります。
問題点1 実装に対するインプットがない
自分で技術選定やロジックを考えたりエラーを修正したりすることによって身につくスキルを習得する機会がなくなってしまうため、コーディングスキルがあまり身につきません。
問題点2 実装内容の正誤判断が難しい
開発経験が浅いためAIが実装した内容に対しての正誤判断がつかず、なんとなくで深く考えずにAIに作業を任せてしまい、知らないうちにルール違反や品質の悪いコードを混ぜてしまうことになりがちです。
解決方法
上記の問題点を解決すべく、自分なりに解決方法を考えてみました。
その1 エージェントは使わない
極論ですが、新卒エンジニアがコーディングスキルを上げることに関してはこれが一番だと思います。
AIはあくまで調べるためのツールとして使うだけで、コーディングは自分で行うことにすればスキルは身につきます。
ただ、業務において迅速な実装が求められる場合、AIエージェントの利用を完全に避けるという選択肢は現実的ではないです。
その2 AIに助けてもらう
実装した内容を、チャット上でAIに質問して詳しく教えてもらう。これがAIエージェントを使いつつ、実装内容を理解するための最善策だと思います。
しかし、チャット上で聞くためにはあらかじめ質問内容を整理する必要があります。
また、チャットは流れてしまうため、聞いただけでは後から見返すのが難しいです。
そこで
AIに助けてもらう作業をさらに効率化するため、AIが実装した内容を詳しく解説したドキュメントを自動生成してくれるエージェントをVSCodeで作ってみました。
チャット上で@studyと打つだけでGemini3.0が解説ドキュメントを生成
まず、何をもってAIが実装した内容だと判断するかですが、gitの最新のコミット内容と現在の作業ディレクトリとの差分をAIによる実装結果と見なすことにしました。
ユースケースとしては、ユーザーはAIエージェントに実装してもらった後、@studyを入力し、生成されたドキュメントを確認して実装内容を理解した後にコミットをする、その後引き続きAIエージェントに実装してもらい、@studyを入力し内容を理解...を繰り返す、みたいな感じです。
開発してみた
VSCodeの拡張機能として実装します。
ディレクトリ構成
study/
├── .github/ # GitHub設定
├── .vscode/
│ └── launch.json # デバッグ設定
├── src/
│ └── extension.ts # メインのエージェント実装
├── out/ # コンパイル後の JavaScript 出力
├── package.json # プロジェクト設定・依存関係
├── package-lock.json # 依存関係ロック
├── tsconfig.json # TypeScript設定
└── node_modules/ # インストール済みパッケージ
実装内容
まず、AIに無視してもらう実装内容とは関係ないディレクトリを指定します。
const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage', 'study'];
次に、activate関数でエージェントの起動と登録をします。
export function activate(context: vscode.ExtensionContext) {
// エージェントの挙動を定義するハンドラー関数
const handler: vscode.ChatRequestHandler = async (request, chatContext, stream, token) => {
/// ユーザーのリクエスト(@study)を受け付け必要な全処理をこの関数内で実行
};
// @studyで呼び出せるように登録
const agent = vscode.chat.createChatParticipant('chat.study', handler);
//拡張機能の解放処理のために登録
context.subscriptions.push(agent);
}
モデルはGemini3.0を使います。
// ステップ4: モデル選択
const allModels = await vscode.lm.selectChatModels({});
let model = allModels.find(m => m.id === 'gemini-3-pro-preview');
if (!model) {
stream.markdown('使用可能な高性能モデルが見つかりません。');
return;
}
stream.markdown(`**使用モデル**: ${model.name}\n\n`);
Gitコミットと現在の実装内容の差分を読み取ります。
//最新のコミット(HEAD)と現在の作業ディレクトリの差分を取得する
function getLatestCommitDiff(rootPath: string): { diffText: string, fileList: string[] } {
try {
// 'git diff HEAD' を実行し、未コミットの差分テキストを取得
const diffText = execSync(`git diff HEAD`, { cwd: rootPath, encoding: 'utf8', maxBuffer: 1024 * 500 }).toString();
const uniqueFiles = new Set<string>(); // 変更ファイル名格納用のSet
const lines = diffText.split('\n'); // 差分テキストを行ごとに分割
// Diffログを行ごとに解析し、ファイル名を抽出
lines.forEach(line => {
// 正規表現で Git Diff のヘッダー行(+++ b/ファイルパス)を照合
const match = line.match(/^\+{3} b\/(.+)/);
if (match && match[1]) {
const filePath = match[1].trim();
// 無視リストに含まれない、有効なファイルパスのみを抽出
if (!IGNORE_DIRS.some(dir => filePath.startsWith(dir)) && filePath !== 'dev/null') {
if (filePath.length > 0) {
uniqueFiles.add(filePath); // リストに追加
}
}
}
});
// 差分が空(変更なし)の場合、処理を中断
if (diffText.trim() === "") {
throw new Error("No uncommitted changes found relative to HEAD.");
}
// 差分テキストと一意のファイルリストを返却
return { diffText, fileList: Array.from(uniqueFiles) };
} catch (e: any) {
// Git操作失敗時、エラーをコンソールに出力し、空の結果を返して安全に終了
console.error('Git operation failed:', e.message);
return { diffText: '', fileList: [] };
}
}
差分があるファイルを特定したら、単なる変更点だけでなく、ファイル全体のコードを渡します。これにより、AIはその変更が既存のロジックのどの位置と関連付けられて挿入されたかを正確に把握できます。
function readTargetFiles(rootPath: string, fileList: string[]): string {
let contextStr = ""; // コンテキストを格納する文字列
const MAX_CONTEXT_LENGTH = 300000; // LLMへの入力文字数の上限
// 変更されたファイルリストを一つずつ処理
for (const relativePath of fileList) {
const fullPath = path.join(rootPath, relativePath);
// ファイルが存在するか確認
if (fs.existsSync(fullPath)) {
try {
// 無視リストの最終チェック
if (IGNORE_DIRS.some(dir => relativePath.startsWith(dir))) continue;
// ファイルの内容を同期的に読み込む
const content = fs.readFileSync(fullPath, 'utf8');
// 読み込んだ内容を整形し、ファイル名ヘッダー付きでコンテキスト文字列に追加
contextStr += `\n--- Full Code: ${relativePath} ---\n${content}\n`;
// 文字数制限を超えた場合、読み込みを停止
if (contextStr.length > MAX_CONTEXT_LENGTH) break;
} catch (e) {} // エラーは無視
}
}
return contextStr; // 構築されたコンテキスト文字列を返却
}
最後に、差分解析用のシステムプロンプトを生成します。
ここのプロンプト次第でAIに教えて欲しい情報をカスタマイズできます。
function generateDiffSystemPrompt(codeContext: string, diffText: string, targetType: string, affectedFiles: string): string {
return `
あなたはコードの設計意図を可視化する専門家です。
AIエージェントが今実行した実装変更(HEADとの差分)に基づき、新卒エンジニアに「AIが何をどのように実装したか」を分かりやすく教えるための実装結果解説ドキュメント(Markdown)を作成してください。
このドキュメントは、AIが追加・変更した内容を新人エンジニアが理解できるように、図解と詳細な解説を提供することに特化しています。
【必須要件: 詳細な解説のためのポイント】
1. Mermaid記法による図解 (最重要)
- 変更が加わったファイル群(${affectedFiles})に焦点を当て、そのファイル間の依存関係や新しく導入されたロジックのフローをMermaid記法(シーケンス図、またはフローチャート)で可視化すること。
- \`\`\`mermaid\`\`\` ブロックを使用し、VS Codeでプレビュー可能な形式にすること。
2. プロジェクト固有の用語集
- AIが今回の作業で新しく導入したクラス名、関数名、変数名を5つ以上ピックアップし、その役割を解説すること。また、関連する既存のドメイン用語も抽出すること。
【出力フォーマット】
# 🤖 AIエージェント実装結果解説:${targetType.toUpperCase()}機能の導入(新人向け)
## 📍 1. AIが変更したアーキテクチャ・マップ (Mermaid図解)
(AIエージェントが今回の変更でどのファイルを修正し、その間にどのようなデータや処理の流れが生まれたかを図で描く。)
## 📖 2. AIが導入した用語の基礎知識
AIの変更内容と関連ファイル全体コードを基に、以下の3種類の用語を抽出して解説してください。
1. 新規導入用語 (最重要):今回のGit差分で新しく追加されたクラス名、関数名、重要な変数名。
2. 既存ドメイン用語:プロジェクト特有の業務知識やドメインを示す略語(例: DTO, Usecase, CRM, SKUなど)で、ファイル名やクラス名に使われているもの。
3. 注意すべき変数:意図的に特殊な値が設定されている定数や、型定義ファイル内で重要な役割を持つ型エイリアス。
| 用語 | ファイル上の位置 | 意味・役割(今回のAIの変更との関連を強調) |
|------|----------------|------------|
| (例) TaskQueue | src/services/task.ts | バックエンドでタスクを非同期処理するためのキュー。AIエージェントが「優先度付き」に変更した。|
| (例) UserType | types/user.ts | ユーザーの種類(Admin, Guest, Standard)を定義した列挙型(Enum)。AIエージェントが'Guest'を追加した。|
## 💡 3. AIによる実装のポイント解説と設計意図
(このセクションを詳細に記述してください。AIがなぜこのようなコードを書いたのか、その設計思想や技術選定の理由を解説する。特に新人が注意すべき実装のベストプラクティスを、AIエージェントの作業と関連付けて説明すること。)
【Gitの差分ログ】
${diffText}
【関連ファイル全体コード】
${codeContext}
`;
}
package.jsonに拡張機能の設定を書きます。
{
"name": "study-agent",
"displayName": "Study Mentor Agent",
"description": "新卒のためのエージェント",
"version": "0.0.1",
"engines": {
"vscode": "^1.90.0"
},
"categories": [
"AI",
"Chat"
],
"main": "./out/extension.js",
"activationEvents": [],
"contributes": {
"chatParticipants": [
{
"id": "chat.study",
"name": "study",
"fullName": "Study Mentor",
"description": "プロジェクトのコードを解説するドキュメントを生成します",
"isSticky": true
}
]
},
@studyでドキュメント生成してもらう
では実際に試してみましょう。
デバッグしてCopilotチャットを開き、@studyと入力し送信します。
ドキュメントの格納場所ですが、@study実行時にプロジェクトのroot配下にstudyというフォルダを生成し、そこにmdファイルとして格納されるようになっています。

ドキュメントを見てみる
実際にGemini3.0で生成されたドキュメントを見てみましょう。
# 🤖 AIエージェント実装結果解説:検索機能と統計情報の導入(新人向け)
今回のAIエージェントの実装は、ユーザー体験(UX)を向上させるための**「タスク検索機能」**と
**「タスク統計情報の表示」**、および**「ダークモード設定の明示化」**です。
## 📍 1. AIが変更したアーキテクチャ・マップ (Mermaid図解)
AIは、データ管理を行うカスタムフック(`useTasks`)に計算ロジックを追加し、UIコンポーネント(`App`)側で入力値に応じたデータの絞り込み(フィルタリング)を行う構造を構築しました。
```mermaid
flowchart TD
User((User))
subgraph "src/hooks/useTasks.ts (データ管理層)"
TaskState[("tasks (State)")]
GetStats["getStats() <br/> (統計計算ロジック)"]
TaskState -.-> GetStats
end
subgraph "src/App.tsx (UI層)"
SearchInput["Search Input <br/> (検索ボックス)"]
QueryState["query (State) <br/> (検索文字列)"]
FilterLogic{"フィルタリング処理 <br/> (includes判定)"}
DerivedData["filteredTasks <br/> (派生データ)"]
StatsUI["統計表示UI <br/> (Pinned/Unpinned)"]
ListUI["タスク一覧UI"]
end
%% 処理フロー
User -->|入力| SearchInput
SearchInput -->|setQuery| QueryState
TaskState -->|全タスク| FilterLogic
QueryState -->|検索ワード| FilterLogic
FilterLogic -->|抽出結果| DerivedData
DerivedData -->|描画| ListUI
GetStats -->|データ取得| StatsUI
classDef logic fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
classDef state fill:#fff3e0,stroke:#e65100,stroke-width:2px;
class FilterLogic,GetStats logic;
class QueryState,TaskState,DerivedData state;
## 📖 2. AIが導入した用語の基礎知識
今回の変更に関連する重要な用語を解説します。特に「派生データ」という考え方はReact開発において非常に重要です。
| 用語 | ファイル上の位置 | 意味・役割(今回のAIの変更との関連を強調) |
|------|----------------|------------|
| **getStats** | `src/hooks/useTasks.ts` | **新規導入**。現在のタスク一覧から「全件数」「ピン留め数」「未ピン留め数」を計算して返す関数。コンポーネント側で計算せず、フック内にロジックを隠蔽しているのが特徴。 |
| **query** | `src/App.tsx` | **新規導入**。ユーザーが検索ボックスに入力した文字列を保持するState変数。この値が変化すると再レンダリングが走り、リストが絞り込まれる。 |
| **filteredTasks** | `src/App.tsx` | **新規導入**。`tasks` と `query` を組み合わせて生成される**派生データ**。Stateではなく、レンダリングのたびに計算される変数。検索結果の表示に使われる。 |
| **darkMode: 'class'** | `tailwind.config.js` | **設定変更**。Tailwind CSSの設定。これまではOSの設定依存だったダークモードを、HTMLクラス(`.dark`)の有無で手動制御できるように明示的に指定した。 |
| **Task** | `src/types/index.ts` | **既存ドメイン用語**。タスク1件分のデータ構造を定義した型。今回の変更では、この型に含まれる `title` や `description` が検索対象となった。 |
| **TASKS_STORAGE_KEY** | `src/hooks/useTasks.ts` | **注意すべき変数**。ローカルストレージにデータを保存する際のキー定数。ここを変更するとユーザーの保存データが読み込めなくなるため注意が必要。 |
## 💡 3. AIによる実装のポイント解説と設計意図
AIエージェントがどのような意図でコードを変更したのか、新人が学ぶべき「React実装のベストプラクティス」と合わせて解説します。
### ① 「派生データ(Derived State)」の活用
`src/App.tsx` において、AIは検索結果を管理するために新しい `useState` を作りませんでした。代わりに、既存の `tasks` と検索ワード `query` を使って、レンダリング中に `filteredTasks` を計算しています。
// ❌ 悪い例: 検索結果もStateで管理してしまう(同期ズレの原因になる)
// const [filteredTasks, setFilteredTasks] = useState([]);
// ✅ AIの実装: 既存のStateから計算する(Single Source of Truth)
const filteredTasks = tasks.filter((t) => { ... });
**解説:** これにより、「タスクが追加されたのに検索結果に反映されない」といったバグを防ぐことができます。**「計算できる値はStateにしない」**はReactの鉄則です。
### ② ロジックの分離(Custom Hookへの委譲)
統計情報を計算する処理を `App.tsx` に直接書かず、`useTasks.ts` 内に `getStats` 関数として実装しました。
**解説:**
* **責務の分離:** UIコンポーネント(App)は「表示」に集中し、データロジック(useTasks)は「計算」に集中させています。
* **再利用性:** 将来、別の画面で統計情報が必要になっても、`getStats` を呼び出すだけで済みます。
### ③ ユーザーへのフィードバック(Empty State)
検索結果が0件だった場合の表示(Empty State)を条件分岐で追加しました。
tsx
{filteredTasks.length === 0 ? (
// "条件に一致するタスクがありません" というメッセージを表示
) : (
// タスクリストを表示
)}
**解説:** 単に何も表示しないのではなく、「検索した結果、何もなかった」ことをユーザーに伝えるのは、親切なUI(UX向上)の基本です。
### ④ Tailwind CSSの設定変更
`tailwind.config.js` に `darkMode: 'class'` を追加しました。
これは、`src/App.tsx` 内にある「ライト/ダークモード切替ボタン」のロジック(`document.documentElement.classList.add('dark')`)を正しく機能させるために必須の設定です。
これがないと、いくらクラスを付け替えてもOSの設定が優先されてしまう場合があります。
AIエージェントに実装してもらった機能(未コミット分)について、しっかり解説されています。
用語解説や設計意図が書いてあるので、わかりやすいですね!
さらに、モデルをGemini2.5に切り替えて出力してもらって、3.0と違いがあるのか比べてみましょう。
# 🤖 AIエージェント実装結果解説:タスク検索・統計表示機能の導入(新人向け)
## 📍 1. AIが変更したアーキテクチャ・マップ (Mermaid図解)
今回のAIによる変更は、主にフロントエンドの表示ロジックに集中しています。ユーザーが検索キーワードを入力すると、`App.tsx`コンポーネント内でタスクがフィルタリングされ、画面が再描画されます。また、タスクの状態(ピン留めされているかなど)を集計するロジックが`useTasks.ts`に追加され、その結果がUIに表示されるようになりました。
```mermaid
sequenceDiagram
participant User
participant App_tsx as "App.tsx (UI)"
participant useTasks_ts as "useTasks.ts (カスタムフック)"
participant tailwind_config as "tailwind.config.js"
Note over App_tsx, tailwind_config: 初期設定
tailwind_config->>App_tsx: 'darkMode: "class"' を有効化
App_tsx->>App_tsx: HTML要素に 'dark' クラスを付与しダークモード適用
Note over User, App_tsx: ユーザー操作 (検索)
User->>App_tsx: 検索ボックスにキーワードを入力
App_tsx->>App_tsx: setQuery(e.target.value) で 'query' state を更新
App_tsx->>App_tsx: 再レンダリングがトリガーされる
Note over App_tsx, useTasks_ts: データのフィルタリングと表示
App_tsx->>useTasks_ts: tasks 配列を取得
useTasks_ts-->>App_tsx: 現在の全タスク(tasks)を返す
App_tsx->>App_tsx: 'query' を基に 'filteredTasks' を生成
App_tsx->>App_tsx: 'filteredTasks' をリストに描画
Note over App_tsx, useTasks_ts: 統計情報の表示
App_tsx->>useTasks_ts: getStats() を呼び出し
useTasks_ts->>useTasks_ts: tasks配列を元にピン留め数を集計
useTasks_ts-->>App_tsx: {pinned, unpinned} を含む統計オブジェクトを返す
App_tsx->>App_tsx: 統計情報をUIに描画
## 📖 2. AIが導入した用語の基礎知識
AIの変更内容を理解するために、今回の実装で新しく導入された、あるいは関連する重要な用語をまとめました。
| 用語 | ファイル上の位置 | 意味・役割(今回のAIの変更との関連を強調) |
|------|----------------|------------|
| `query` | `src/App.tsx` | **【新規導入】** ユーザーが検索ボックスに入力した文字列を保持するためのReact state変数です。AIはこのstateを基にタスクの絞り込みを行います。 |
| `filteredTasks` | `src/App.tsx` | **【新規導入】** `tasks`配列(全タスク)から`query`の内容に一致するタスクだけを抽出して格納する派生変数です。AIはこの配列を画面描画に使うことで、検索結果をリアルタイムに表示しています。 |
| `getStats` | `src/hooks/useTasks.ts` | **【新規導入】** タスクに関する統計情報(合計数、ピン留めされた数、されていない数)を計算して返す新しい関数です。AIはこれをカスタムフックに追加し、UIコンポーネントから簡単に統計情報を取得できるようにしました。 |
| `darkMode: 'class'` | `tailwind.config.js` | **【新規導入】** Tailwind CSSの設定で、ダークモードの切り替えをJavaScript(クラスの付け外し)で制御できるようにするオプションです。AIはこれを追加し、`App.tsx`内の`isDark` stateと連動させています。 |
| `setQuery` | `src/App.tsx` | **【新規導入】** `query` stateを更新するための関数です。検索ボックスの`onChange`イベントで呼び出され、ユーザーの入力を`query`に反映させます。 |
| `useTasks` | `src/hooks/useTasks.ts` | 【既存ドメイン用語】タスクデータの状態管理(取得、追加、更新、削除など)に関するロジックをまとめたカスタムフックです。AIはここに`getStats`関数を追加し、責務を維持したまま機能を拡張しました。 |
| `Task` | `src/types.ts` | 【既存ドメイン用語】タスク一個分のデータ構造を定義した型です。`title`や`description`といったプロパティが含まれており、AIはこれらのプロパティを検索対象として利用しています。 |
| `TASKS_STORAGE_KEY` | `src/hooks/useTasks.ts` | 【注意すべき変数】タスクデータをブラウザの`localStorage`に保存する際のキーとなる定数です。デバッグ時に`localStorage`を確認する際にこのキー名を知っていると便利です。 |
## 💡 3. AIによる実装のポイント解説と設計意図
今回のAIによる実装は、既存のアーキテクチャを尊重しつつ、ユーザー体験を向上させるための機能追加が中心です。新人エンジニアが学ぶべきポイントは以下の通りです。
### 1. 状態管理と派生データの分離
AIは、**「ユーザーが入力した検索キーワード(`query`)」**という新しい状態を`useState`で定義しました。そして、**「検索結果(`filteredTasks`)」**は、この`query`と既存の`tasks`から計算される**派生データ**として扱っています。
```typescript:src/App.tsx
// 元となるデータソース
const { tasks, ... } = useTasks();
// ユーザー入力によってのみ変化する「状態」
const [query, setQuery] = useState('');
// 「状態」と「データソース」から計算される「派生データ」
const filteredTasks = tasks.filter((t) => {
if (!query.trim()) return true;
// ... filtering logic ...
});
// 描画には「派生データ」を使用する
return (
// ...
{filteredTasks.map((task) => (
// ...
))}
// ...
);
このように「状態」と「派生データ」を明確に分離することは、React開発のベストプラクティスです。`filteredTasks`を`useState`で管理するのではなく、レンダリングのたびに再計算することで、状態の不整合を防ぎ、コードをシンプルに保つことができます。
### 2. カスタムフックによる関心の分離
タスクの統計情報を計算する`getStats`関数は、`App.tsx`コンポーネント内ではなく、タスク関連のロジックがすべて集約されている`useTasks.ts`カスタムフック内に追加されました。
**【設計意図】**
* **責務の集約**: 「タスクに関するデータ操作」は`useTasks`フックが担う、という設計原則を一貫させています。これにより、`App.tsx`はUIの表示ロジックに集中でき、コードの見通しが良くなります。
* **再利用性の向上**: もし将来、別のコンポーネントでもタスクの統計情報が必要になった場合、`useTasks`フックを呼び出すだけで`getStats`関数を簡単に利用できます。
### 3. UIとロジックの連動
`tailwind.config.js`に`darkMode: 'class'`を追加したことで、CSSのダークモード適用条件が`prefers-color-scheme`メディアクエリから、HTMLのルート要素に`.dark`クラスが存在するかどうかに変更されました。
これに合わせて`App.tsx`では`useEffect`を使い、`isDark`というReactのstateとHTMLのクラスを同期させています。
```typescript:src/App.tsx
useEffect(() => {
if (isDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
// ...
}, [isDark]);
これにより、OSの設定とは独立して、ユーザーがアプリケーション内で能動的にテーマを切り替える機能を実現しています。これは、UIの状態(React state)と実際のDOMの状態を同期させる、Reactの基本的ながら非常に重要なテクニックです。
あまり差は無いように見えますね。
しかし、AIが導入した用語の基礎知識のところをよく見比べてみると、少し書き方に違いがあります。
Gemini2.5の出力は実装内容の正確な翻訳(実装ベース)に優れていますが、Gemini 3.0は実装ベースというよりかは体系的な知識習得ができるように解説しているように感じます。
各モデルの得意とする特性を活かせるようにプロンプトを調整すれば、得られる情報量と解説の質はさらに向上すると思います。
まとめ
いかがだったでしょうか?
これで新卒エンジニアは悩むことなくAIエージェントを使った開発を進められますね。
どんどんAI使っていきましょう!
