AIコーディングアシスタント完全ガイド2025 - GitHub Copilot vs Claude vs Cursor徹底比較
2025年最新のAIコーディングアシスタントを徹底比較。GitHub Copilot、Claude Sonnet、Cursor IDE、Windsurf、Amazon Q Developerの機能・価格・生産性向上効果を実例とともに解説。開発効率を最大70%向上させる選び方と活用法を紹介します。
AIとのペアプログラミングを効果的に行うための実践的テクニックを解説。効果的なプロンプトの書き方、コードレビュー、リファクタリング、デバッグ、チーム導入まで、具体例と共に紹介します。
AI ペアプログラミングは、人間の創造性と AI の処理能力を組み合わせた次世代の開発手法です。本記事では、AI と効果的に協働するための実践的なテクニックを、豊富な実例と共に解説します。
項目 | 人間同士のペアプロ | AIペアプロ | 効果 |
---|---|---|---|
可用性 | 相手のスケジュール調整必要 | 24時間365日利用可能 | ⭐⭐⭐⭐⭐ |
知識範囲 | 個人の経験・専門分野に依存 | 広範な言語・フレームワーク対応 | ⭐⭐⭐⭐☆ |
学習速度 | 経験の蓄積に時間が必要 | 最新情報を即座に反映 | ⭐⭐⭐⭐⭐ |
コミュニケーション | 自然な対話・暗黙知の共有 | プロンプトの精度に依存 | ⭐⭐⭐☆☆ |
創造性 | 人間の直感・創造性 | パターンベースの提案 | ⭐⭐☆☆☆ |
コスト | 人件費が必要 | サブスクリプション費用 | ⭐⭐⭐⭐☆ |
チャートを読み込み中...
// ❌ 曖昧な指示
"ユーザー認証機能を実装して"
// ❌ 不十分な情報
"Reactでフォームを作って"
// ✅ 明確で詳細な指示
"Next.js 14 App RouterでJWT認証システムを実装してください。
要件:
- メールとパスワードでのログイン
- リフレッシュトークンの実装
- ミドルウェアでの認証チェック
- TypeScriptで型安全に実装
- Prismaを使用したユーザーモデル"
// ✅ 技術スタックと制約を明記
"React 18とreact-hook-formを使用して、以下の要件を満たすユーザー登録フォームを実装してください:
- zodでバリデーション
- エラーメッセージの日本語表示
- 送信中のローディング状態
- アクセシビリティ対応(ARIA属性)"
// ❌ 曖昧な指示
"ユーザー認証機能を実装して"
// ❌ 不十分な情報
"Reactでフォームを作って"
// ✅ 明確で詳細な指示
"Next.js 14 App RouterでJWT認証システムを実装してください。
要件:
- メールとパスワードでのログイン
- リフレッシュトークンの実装
- ミドルウェアでの認証チェック
- TypeScriptで型安全に実装
- Prismaを使用したユーザーモデル"
// ✅ 技術スタックと制約を明記
"React 18とreact-hook-formを使用して、以下の要件を満たすユーザー登録フォームを実装してください:
- zodでバリデーション
- エラーメッセージの日本語表示
- 送信中のローディング状態
- アクセシビリティ対応(ARIA属性)"
// プロンプト: "商品検索APIのエンドポイントを実装"
// TypeScript, Express, Prismaを使用
import { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
const prisma = new PrismaClient();
// 検索パラメータのスキーマ
const searchSchema = z.object({
query: z.string().min(1).max(100),
category: z.string().optional(),
minPrice: z.number().min(0).optional(),
maxPrice: z.number().min(0).optional(),
sortBy: z.enum(['price', 'name', 'createdAt']).optional(),
order: z.enum(['asc', 'desc']).optional(),
page: z.number().int().min(1).default(1),
limit: z.number().int().min(1).max(100).default(20)
});
export async function searchProducts(req: Request, res: Response) {
try {
// パラメータのバリデーション
const params = searchSchema.parse({
...req.query,
minPrice: req.query.minPrice ? Number(req.query.minPrice) : undefined,
maxPrice: req.query.maxPrice ? Number(req.query.maxPrice) : undefined,
page: Number(req.query.page) || 1,
limit: Number(req.query.limit) || 20
});
// 検索条件の構築
const where = {
AND: [
{
OR: [
{ name: { contains: params.query, mode: 'insensitive' } },
{ description: { contains: params.query, mode: 'insensitive' } }
]
},
params.category ? { category: { name: params.category } } : {},
params.minPrice ? { price: { gte: params.minPrice } } : {},
params.maxPrice ? { price: { lte: params.maxPrice } } : {}
]
};
// ページネーション計算
const skip = (params.page - 1) * params.limit;
// データ取得
const [products, totalCount] = await Promise.all([
prisma.product.findMany({
where,
skip,
take: params.limit,
orderBy: params.sortBy
? { [params.sortBy]: params.order || 'asc' }
: undefined,
include: {
category: true,
images: true
}
}),
prisma.product.count({ where })
]);
res.json({
products,
pagination: {
page: params.page,
limit: params.limit,
totalCount,
totalPages: Math.ceil(totalCount / params.limit)
}
});
} catch (error) {
next(error);
}
}
// プロンプト: "先ほどの検索APIにエラーハンドリングを追加"
import { ErrorRequestHandler } from 'express';
// カスタムエラークラス
export class APIError extends Error {
constructor(
public statusCode: number,
message: string,
public code?: string
) {
super(message);
this.name = 'APIError';
}
}
// エラーハンドリングミドルウェア
export const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
console.error('Error:', err);
// Zodバリデーションエラー
if (err.name === 'ZodError') {
return res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid request parameters',
details: err.errors.map(e => ({
field: e.path.join('.'),
message: e.message
}))
}
});
}
// Prismaエラー
if (err.code === 'P2002') {
return res.status(409).json({
error: {
code: 'DUPLICATE_ERROR',
message: 'Resource already exists'
}
});
}
// カスタムAPIエラー
if (err instanceof APIError) {
return res.status(err.statusCode).json({
error: {
code: err.code || 'API_ERROR',
message: err.message
}
});
}
// 予期しないエラー
res.status(500).json({
error: {
code: 'INTERNAL_SERVER_ERROR',
message: process.env.NODE_ENV === 'production'
? 'An unexpected error occurred'
: err.message
}
});
};
// 改善された検索関数
export async function searchProductsWithErrorHandling(req: Request, res: Response, next: NextFunction) {
try {
const params = searchSchema.parse(req.query);
// タイムアウト処理
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new APIError(504, 'Request timeout')), 5000)
);
const searchPromise = performSearch(params);
const result = await Promise.race([searchPromise, timeoutPromise]);
res.json(result);
} catch (error) {
next(error);
}
}
// プロンプト: "検索APIのパフォーマンスを最適化"
import { Redis } from 'ioredis';
import { compress, decompress } from 'lz-string';
const redis = new Redis();
// キャッシュキー生成
function generateCacheKey(params: any): string {
return `search:${JSON.stringify(params)}`;
}
// 最適化された検索関数
export async function optimizedSearchProducts(req: Request, res: Response, next: NextFunction) {
try {
const params = searchSchema.parse(req.query);
const cacheKey = generateCacheKey(params);
// キャッシュチェック
const cached = await redis.get(cacheKey);
if (cached) {
const data = JSON.parse(decompress(cached));
res.set('X-Cache', 'HIT');
return res.json(data);
}
// 検索クエリの最適化
const products = await prisma.product.findMany({
where: buildWhereClause(params),
skip: (params.page - 1) * params.limit,
take: params.limit,
select: {
id: true,
name: true,
price: true,
description: true,
thumbnail: true,
category: {
select: { id: true, name: true }
},
_count: {
select: { reviews: true }
}
}
});
// 集計を別クエリで実行(並列化)
const [enrichedProducts, totalCount] = await Promise.all([
// 画像URLを一括取得
enrichProductsWithImages(products),
// カウントクエリ(インデックス使用)
prisma.product.count({
where: buildWhereClause(params)
})
]);
const result = {
products: enrichedProducts,
pagination: {
page: params.page,
limit: params.limit,
totalCount,
totalPages: Math.ceil(totalCount / params.limit)
}
};
// 圧縮してキャッシュ
await redis.setex(
cacheKey,
300, // 5分間キャッシュ
compress(JSON.stringify(result))
);
res.set('X-Cache', 'MISS');
res.json(result);
} catch (error) {
next(error);
}
}
// インデックスを考慮したWHERE句構築
function buildWhereClause(params: SearchParams) {
const conditions: any[] = [];
// フルテキスト検索の利用
if (params.query) {
conditions.push({
OR: [
{ searchVector: { search: params.query } },
{ name: { contains: params.query, mode: 'insensitive' } }
]
});
}
// インデックスされたフィールドでのフィルタリング
if (params.category) {
conditions.push({ categoryId: params.category });
}
// 範囲検索の最適化
if (params.minPrice || params.maxPrice) {
conditions.push({
price: {
...(params.minPrice && { gte: params.minPrice }),
...(params.maxPrice && { lte: params.maxPrice })
}
});
}
return conditions.length > 0 ? { AND: conditions } : {};
}
// プロンプト: "検索APIの包括的なテストを作成"
import request from 'supertest';
import { app } from '../app';
import { prisma } from '../prisma';
describe('Product Search API', () => {
beforeEach(async () => {
// テストデータのセットアップ
await prisma.product.createMany({
data: [
{
name: 'iPhone 15 Pro',
description: '最新のiPhone',
price: 159800,
categoryId: 'electronics'
},
{
name: 'MacBook Pro',
description: '高性能ノートPC',
price: 248800,
categoryId: 'electronics'
},
{
name: 'プログラミング入門',
description: 'Python/JavaScript対応',
price: 2980,
categoryId: 'books'
}
]
});
});
afterEach(async () => {
await prisma.product.deleteMany();
});
describe('基本的な検索機能', () => {
test('キーワードで商品を検索できる', async () => {
const response = await request(app)
.get('/api/products/search')
.query({ query: 'iPhone' })
.expect(200);
expect(response.body.products).toHaveLength(1);
expect(response.body.products[0].name).toBe('iPhone 15 Pro');
});
test('大文字小文字を区別せずに検索できる', async () => {
const response = await request(app)
.get('/api/products/search')
.query({ query: 'iphone' })
.expect(200);
expect(response.body.products).toHaveLength(1);
});
});
describe('フィルタリング機能', () => {
test('カテゴリーでフィルタリングできる', async () => {
const response = await request(app)
.get('/api/products/search')
.query({
query: '',
category: 'electronics'
})
.expect(200);
expect(response.body.products).toHaveLength(2);
});
test('価格範囲でフィルタリングできる', async () => {
const response = await request(app)
.get('/api/products/search')
.query({
query: '',
minPrice: 100000,
maxPrice: 200000
})
.expect(200);
expect(response.body.products).toHaveLength(1);
expect(response.body.products[0].name).toBe('iPhone 15 Pro');
});
});
describe('ページネーション', () => {
test('ページサイズを指定できる', async () => {
const response = await request(app)
.get('/api/products/search')
.query({
query: '',
limit: 2,
page: 1
})
.expect(200);
expect(response.body.products).toHaveLength(2);
expect(response.body.pagination.totalCount).toBe(3);
expect(response.body.pagination.totalPages).toBe(2);
});
});
describe('エラーハンドリング', () => {
test('無効なパラメータでエラーを返す', async () => {
const response = await request(app)
.get('/api/products/search')
.query({
query: '', // 必須パラメータが空
page: 'invalid' // 数値であるべき
})
.expect(400);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
});
});
describe('パフォーマンス', () => {
test('大量データでも高速に動作する', async () => {
// 1000件のテストデータを作成
const products = Array.from({ length: 1000 }, (_, i) => ({
name: `Product ${i}`,
description: `Description ${i}`,
price: Math.random() * 100000,
categoryId: i % 2 === 0 ? 'electronics' : 'books'
}));
await prisma.product.createMany({ data: products });
const start = Date.now();
await request(app)
.get('/api/products/search')
.query({
query: 'Product',
limit: 20
})
.expect(200);
const duration = Date.now() - start;
expect(duration).toBeLessThan(100); // 100ms以内
});
});
});
チャートを読み込み中...
// プロンプト: "以下のコードのセキュリティ脆弱性をチェックして改善案を提示"
// 元のコード(脆弱性あり)
app.post('/api/users/login', async (req, res) => {
const { email, password } = req.body;
// ❌ SQLインジェクションの脆弱性
const user = await db.query(
`SELECT * FROM users WHERE email = '${email}'`
);
// ❌ タイミング攻撃の脆弱性
if (user && user.password === password) {
// ❌ セッション固定攻撃の脆弱性
req.session.userId = user.id;
res.json({ success: true });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
// AIによる改善提案
import bcrypt from 'bcrypt';
import { z } from 'zod';
import rateLimit from 'express-rate-limit';
import helmet from 'helmet';
// 入力検証スキーマ
const loginSchema = z.object({
email: z.string().email().max(255),
password: z.string().min(8).max(128)
});
// レート制限
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 5, // 最大5回
message: 'Too many login attempts',
standardHeaders: true,
legacyHeaders: false,
});
// セキュアな実装
app.post('/api/users/login',
loginLimiter,
async (req, res, next) => {
try {
// 入力検証
const { email, password } = loginSchema.parse(req.body);
// パラメータ化クエリでSQLインジェクション対策
const user = await db.user.findUnique({
where: { email },
select: {
id: true,
email: true,
passwordHash: true,
isActive: true,
failedLoginAttempts: true,
lastFailedLogin: true
}
});
// アカウントロックチェック
if (user && user.failedLoginAttempts >= 5) {
const lockDuration = 30 * 60 * 1000; // 30分
const timeSinceLastFail = Date.now() - user.lastFailedLogin.getTime();
if (timeSinceLastFail < lockDuration) {
return res.status(423).json({
error: 'Account temporarily locked'
});
}
}
// タイミング攻撃対策(常に同じ時間で応答)
const dummyHash = '$2b$10$dummyhashfortimingatttackprotection';
const hashToCheck = user ? user.passwordHash : dummyHash;
const isValid = await bcrypt.compare(password, hashToCheck);
if (!user || !isValid || !user.isActive) {
// 失敗回数を記録
if (user) {
await db.user.update({
where: { id: user.id },
data: {
failedLoginAttempts: { increment: 1 },
lastFailedLogin: new Date()
}
});
}
// 汎用的なエラーメッセージ
return res.status(401).json({
error: 'Invalid credentials'
});
}
// 成功時は失敗回数をリセット
await db.user.update({
where: { id: user.id },
data: {
failedLoginAttempts: 0,
lastLogin: new Date()
}
});
// セッション再生成でセッション固定攻撃対策
req.session.regenerate((err) => {
if (err) return next(err);
req.session.userId = user.id;
req.session.loginTime = Date.now();
res.json({
success: true,
user: {
id: user.id,
email: user.email
}
});
});
} catch (error) {
next(error);
}
}
);
AIに要件を伝えてテストケースを生成
失敗するテストを確認
AIの提案を参考に最小限の実装
テストが通ることを確認
AIと共にコードを改善
// プロンプト: "ECサイトのショッピングカート機能のテストケースを作成"
import { ShoppingCart } from '../ShoppingCart';
import { Product } from '../types';
describe('ShoppingCart', () => {
let cart: ShoppingCart;
beforeEach(() => {
cart = new ShoppingCart();
});
describe('商品の追加', () => {
test('商品を追加できる', () => {
const product: Product = {
id: '1',
name: 'テスト商品',
price: 1000,
stock: 10
};
cart.addItem(product, 2);
expect(cart.getItems()).toHaveLength(1);
expect(cart.getItems()[0]).toMatchObject({
product,
quantity: 2
});
});
test('同じ商品を追加すると数量が増える', () => {
const product: Product = {
id: '1',
name: 'テスト商品',
price: 1000,
stock: 10
};
cart.addItem(product, 2);
cart.addItem(product, 3);
expect(cart.getItems()).toHaveLength(1);
expect(cart.getItems()[0].quantity).toBe(5);
});
test('在庫を超える数量は追加できない', () => {
const product: Product = {
id: '1',
name: 'テスト商品',
price: 1000,
stock: 5
};
expect(() => cart.addItem(product, 10))
.toThrow('在庫が不足しています');
});
});
describe('商品の削除', () => {
test('商品を削除できる', () => {
const product: Product = {
id: '1',
name: 'テスト商品',
price: 1000,
stock: 10
};
cart.addItem(product, 2);
cart.removeItem(product.id);
expect(cart.getItems()).toHaveLength(0);
});
});
describe('数量の更新', () => {
test('数量を更新できる', () => {
const product: Product = {
id: '1',
name: 'テスト商品',
price: 1000,
stock: 10
};
cart.addItem(product, 2);
cart.updateQuantity(product.id, 5);
expect(cart.getItems()[0].quantity).toBe(5);
});
test('数量を0にすると商品が削除される', () => {
const product: Product = {
id: '1',
name: 'テスト商品',
price: 1000,
stock: 10
};
cart.addItem(product, 2);
cart.updateQuantity(product.id, 0);
expect(cart.getItems()).toHaveLength(0);
});
});
describe('合計計算', () => {
test('小計を正しく計算できる', () => {
cart.addItem({ id: '1', name: '商品1', price: 1000, stock: 10 }, 2);
cart.addItem({ id: '2', name: '商品2', price: 500, stock: 10 }, 3);
expect(cart.getSubtotal()).toBe(3500);
});
test('税込み合計を計算できる', () => {
cart.addItem({ id: '1', name: '商品1', price: 1000, stock: 10 }, 1);
expect(cart.getTotal({ taxRate: 0.1 })).toBe(1100);
});
test('送料を含む合計を計算できる', () => {
cart.addItem({ id: '1', name: '商品1', price: 1000, stock: 10 }, 1);
expect(cart.getTotal({
taxRate: 0.1,
shippingFee: 500
})).toBe(1600);
});
});
});
// プロンプト: "テストを通すための最小限の実装"
export interface Product {
id: string;
name: string;
price: number;
stock: number;
}
export interface CartItem {
product: Product;
quantity: number;
}
export interface TotalOptions {
taxRate?: number;
shippingFee?: number;
}
export class ShoppingCart {
private items: Map<string, CartItem> = new Map();
addItem(product: Product, quantity: number): void {
if (quantity <= 0) {
throw new Error('数量は1以上である必要があります');
}
const existingItem = this.items.get(product.id);
const totalQuantity = existingItem
? existingItem.quantity + quantity
: quantity;
if (totalQuantity > product.stock) {
throw new Error('在庫が不足しています');
}
if (existingItem) {
existingItem.quantity = totalQuantity;
} else {
this.items.set(product.id, { product, quantity });
}
}
removeItem(productId: string): void {
this.items.delete(productId);
}
updateQuantity(productId: string, quantity: number): void {
if (quantity === 0) {
this.removeItem(productId);
return;
}
const item = this.items.get(productId);
if (!item) {
throw new Error('商品がカートに存在しません');
}
if (quantity > item.product.stock) {
throw new Error('在庫が不足しています');
}
item.quantity = quantity;
}
getItems(): CartItem[] {
return Array.from(this.items.values());
}
getSubtotal(): number {
return Array.from(this.items.values()).reduce(
(total, item) => total + item.product.price * item.quantity,
0
);
}
getTotal(options: TotalOptions = {}): number {
const subtotal = this.getSubtotal();
const tax = subtotal * (options.taxRate || 0);
const shipping = options.shippingFee || 0;
return subtotal + tax + shipping;
}
clear(): void {
this.items.clear();
}
isEmpty(): boolean {
return this.items.size === 0;
}
}
// プロンプト: "ShoppingCartクラスをリファクタリングして、より拡張性の高い設計に"
// リファクタリング後の実装
// 戦略パターンで税計算を拡張可能に
interface TaxCalculator {
calculate(amount: number): number;
}
class JapaneseTaxCalculator implements TaxCalculator {
constructor(private rate: number = 0.1) {}
calculate(amount: number): number {
return Math.floor(amount * this.rate);
}
}
// 送料計算も戦略パターンで
interface ShippingCalculator {
calculate(items: CartItem[], subtotal: number): number;
}
class FlatRateShipping implements ShippingCalculator {
constructor(private fee: number) {}
calculate(): number {
return this.fee;
}
}
class FreeShippingOver implements ShippingCalculator {
constructor(
private threshold: number,
private fee: number
) {}
calculate(items: CartItem[], subtotal: number): number {
return subtotal >= this.threshold ? 0 : this.fee;
}
}
// イベント駆動で拡張性を向上
type CartEventType = 'item-added' | 'item-removed' | 'item-updated' | 'cart-cleared';
interface CartEvent {
type: CartEventType;
data: any;
}
type CartEventListener = (event: CartEvent) => void;
// 改善されたShoppingCart
export class ShoppingCart {
private items: Map<string, CartItem> = new Map();
private listeners: Map<CartEventType, CartEventListener[]> = new Map();
constructor(
private taxCalculator: TaxCalculator = new JapaneseTaxCalculator(),
private shippingCalculator: ShippingCalculator = new FlatRateShipping(0)
) {}
on(event: CartEventType, listener: CartEventListener): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(listener);
}
private emit(event: CartEvent): void {
const listeners = this.listeners.get(event.type) || [];
listeners.forEach(listener => listener(event));
}
addItem(product: Product, quantity: number): void {
this.validateQuantity(quantity);
this.validateStock(product, quantity);
const existingItem = this.items.get(product.id);
if (existingItem) {
const newQuantity = existingItem.quantity + quantity;
this.validateStock(product, newQuantity);
existingItem.quantity = newQuantity;
} else {
this.items.set(product.id, { product, quantity });
}
this.emit({
type: 'item-added',
data: { product, quantity }
});
}
private validateQuantity(quantity: number): void {
if (quantity <= 0) {
throw new CartError('INVALID_QUANTITY', '数量は1以上である必要があります');
}
}
private validateStock(product: Product, quantity: number): void {
if (quantity > product.stock) {
throw new CartError('INSUFFICIENT_STOCK', '在庫が不足しています');
}
}
// その他のメソッドも同様にリファクタリング
getTotalBreakdown(): {
subtotal: number;
tax: number;
shipping: number;
total: number;
} {
const subtotal = this.getSubtotal();
const tax = this.taxCalculator.calculate(subtotal);
const shipping = this.shippingCalculator.calculate(
this.getItems(),
subtotal
);
return {
subtotal,
tax,
shipping,
total: subtotal + tax + shipping
};
}
}
// カスタムエラークラス
class CartError extends Error {
constructor(public code: string, message: string) {
super(message);
this.name = 'CartError';
}
}
// プロンプト: "ショッピングカートの統合テストを作成"
import { ShoppingCart } from '../ShoppingCart';
import { InventoryService } from '../services/InventoryService';
import { PriceService } from '../services/PriceService';
import { PromotionService } from '../services/PromotionService';
// モックサービス
jest.mock('../services/InventoryService');
jest.mock('../services/PriceService');
jest.mock('../services/PromotionService');
describe('ShoppingCart統合テスト', () => {
let cart: ShoppingCart;
let inventoryService: jest.Mocked<InventoryService>;
let priceService: jest.Mocked<PriceService>;
let promotionService: jest.Mocked<PromotionService>;
beforeEach(() => {
inventoryService = new InventoryService() as jest.Mocked<InventoryService>;
priceService = new PriceService() as jest.Mocked<PriceService>;
promotionService = new PromotionService() as jest.Mocked<PromotionService>;
cart = new ShoppingCart({
inventoryService,
priceService,
promotionService
});
});
describe('在庫連携', () => {
test('リアルタイム在庫チェック', async () => {
const product = {
id: '1',
name: 'Limited Edition',
price: 5000,
stock: 3
};
inventoryService.checkStock.mockResolvedValue({
available: 2,
reserved: 1
});
await expect(cart.addItemAsync(product, 3))
.rejects.toThrow('在庫が不足しています');
await cart.addItemAsync(product, 2);
expect(cart.getItems()).toHaveLength(1);
});
test('在庫予約処理', async () => {
const product = {
id: '1',
name: 'Popular Item',
price: 3000,
stock: 10
};
inventoryService.reserveStock.mockResolvedValue({
reservationId: 'res-123',
expiresAt: new Date(Date.now() + 10 * 60 * 1000)
});
await cart.addItemAsync(product, 2);
expect(inventoryService.reserveStock)
.toHaveBeenCalledWith(product.id, 2);
});
});
describe('価格連携', () => {
test('動的価格の適用', async () => {
const product = {
id: '1',
name: 'Dynamic Price Item',
price: 1000,
stock: 10
};
priceService.getCurrentPrice.mockResolvedValue({
basePrice: 1000,
currentPrice: 900,
discount: 10
});
await cart.addItemAsync(product, 1);
const total = await cart.calculateTotalAsync();
expect(total.subtotal).toBe(900);
});
});
describe('プロモーション連携', () => {
test('クーポンコードの適用', async () => {
const items = [
{ id: '1', name: 'Item 1', price: 1000, stock: 10 },
{ id: '2', name: 'Item 2', price: 2000, stock: 10 }
];
for (const item of items) {
await cart.addItemAsync(item, 1);
}
promotionService.applyPromotion.mockResolvedValue({
type: 'percentage',
value: 20,
appliedAmount: 600
});
const result = await cart.applyPromotionCode('SAVE20');
expect(result.discount).toBe(600);
expect(result.finalAmount).toBe(2400);
});
test('複数プロモーションの組み合わせ', async () => {
promotionService.getApplicablePromotions.mockResolvedValue([
{
id: 'promo1',
type: 'buy-one-get-one',
conditions: { productId: '1' }
},
{
id: 'promo2',
type: 'percentage',
value: 10,
conditions: { minAmount: 5000 }
}
]);
const product = {
id: '1',
name: 'BOGO Item',
price: 3000,
stock: 10
};
await cart.addItemAsync(product, 2);
const promotions = await cart.getApplicablePromotions();
expect(promotions).toHaveLength(2);
expect(promotions[0].type).toBe('buy-one-get-one');
});
});
describe('チェックアウトフロー', () => {
test('完全なチェックアウトプロセス', async () => {
// 商品追加
await cart.addItemAsync(
{ id: '1', name: 'Product 1', price: 2000, stock: 5 },
2
);
// プロモーション適用
await cart.applyPromotionCode('WELCOME10');
// 送料計算
const shippingOptions = await cart.getShippingOptions({
zipCode: '100-0001',
country: 'JP'
});
await cart.selectShipping(shippingOptions[0].id);
// 最終確認
const checkout = await cart.prepareCheckout();
expect(checkout).toMatchObject({
items: expect.any(Array),
subtotal: 4000,
discount: 400,
shipping: expect.any(Number),
tax: expect.any(Number),
total: expect.any(Number),
reservations: expect.any(Array)
});
});
});
});
// プロンプトテンプレート集
// 1. エラー解析
"以下のエラーが発生しています。原因と解決方法を教えてください:
エラーメッセージ: [エラー内容]
発生箇所: [ファイル名:行番号]
実行環境: [Node.js/ブラウザのバージョン]
関連コード: [エラー周辺のコード]"
// 2. パフォーマンス問題
"以下のコードが遅い原因を分析して最適化案を提示してください:
処理内容: [何をする処理か]
データ量: [処理するデータの規模]
現在の処理時間: [実測値]
期待する処理時間: [目標値]
コード: [問題のコード]"
// 3. 不具合の原因調査
"以下の不具合の原因を特定してください:
期待する動作: [正しい動作]
実際の動作: [現在の動作]
再現手順: [1. xxx, 2. yyy]
関連コード: [疑わしいコード部分]
ログ出力: [関連するログ]"
// ユーザーからの報告:
// "アプリを長時間使用するとメモリ使用量が増え続ける"
class EventManager {
private listeners: Map<string, Function[]> = new Map();
on(event: string, callback: Function) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(callback);
}
emit(event: string, data: any) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(cb => cb(data));
}
}
class DataFetcher {
private cache: Map<string, any> = new Map();
private eventManager: EventManager;
constructor(eventManager: EventManager) {
this.eventManager = eventManager;
// 問題:リスナーが削除されない
this.eventManager.on('refresh', () => {
this.fetchData();
});
}
async fetchData() {
const data = await fetch('/api/data').then(r => r.json());
// 問題:キャッシュが無限に増える
this.cache.set(Date.now().toString(), data);
return data;
}
}
// 使用例
const eventManager = new EventManager();
// 問題:インスタンスが作成され続ける
setInterval(() => {
new DataFetcher(eventManager);
}, 1000);
// AIによる分析と修正案:
// 1. イベントリスナーの適切な削除
// 2. キャッシュサイズの制限
// 3. WeakMapの使用でガベージコレクション対応
class EventManager {
private listeners: Map<string, Set<WeakRef<Function>>> = new Map();
on(event: string, callback: Function): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
const weakRef = new WeakRef(callback);
this.listeners.get(event)!.add(weakRef);
// クリーンアップ関数を返す
return () => {
const set = this.listeners.get(event);
if (set) {
set.delete(weakRef);
}
};
}
emit(event: string, data: any) {
const refs = this.listeners.get(event);
if (!refs) return;
const deadRefs: WeakRef<Function>[] = [];
refs.forEach(ref => {
const callback = ref.deref();
if (callback) {
callback(data);
} else {
deadRefs.push(ref);
}
});
// 死んだ参照を削除
deadRefs.forEach(ref => refs.delete(ref));
}
}
class DataFetcher {
private cache: LRUCache<string, any>;
private eventManager: EventManager;
private cleanup: (() => void) | null = null;
private abortController: AbortController | null = null;
constructor(eventManager: EventManager) {
this.eventManager = eventManager;
// LRUキャッシュで自動的に古いエントリを削除
this.cache = new LRUCache({
max: 100, // 最大100エントリ
ttl: 1000 * 60 * 5, // 5分でTTL
updateAgeOnGet: true
});
// イベントリスナーのクリーンアップ関数を保存
this.cleanup = this.eventManager.on('refresh', () => {
this.fetchData();
});
}
async fetchData() {
// 既存のリクエストをキャンセル
if (this.abortController) {
this.abortController.abort();
}
this.abortController = new AbortController();
try {
const response = await fetch('/api/data', {
signal: this.abortController.signal
});
const data = await response.json();
// キャッシュキーを意味のあるものに
const cacheKey = `data-${new Date().toISOString()}`;
this.cache.set(cacheKey, data);
return data;
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Fetch error:', error);
}
throw error;
}
}
// 重要:クリーンアップメソッド
destroy() {
if (this.cleanup) {
this.cleanup();
this.cleanup = null;
}
if (this.abortController) {
this.abortController.abort();
this.abortController = null;
}
this.cache.clear();
}
}
// 使用例(修正版)
const eventManager = new EventManager();
const fetchers = new Set<DataFetcher>();
// インスタンス管理
const intervalId = setInterval(() => {
// 古いインスタンスをクリーンアップ
if (fetchers.size >= 10) {
const oldest = fetchers.values().next().value;
oldest.destroy();
fetchers.delete(oldest);
}
const fetcher = new DataFetcher(eventManager);
fetchers.add(fetcher);
}, 1000);
// アプリケーション終了時のクリーンアップ
process.on('SIGTERM', () => {
clearInterval(intervalId);
fetchers.forEach(f => f.destroy());
process.exit(0);
});
// ユーザーからの報告:
// "アプリを長時間使用するとメモリ使用量が増え続ける"
class EventManager {
private listeners: Map<string, Function[]> = new Map();
on(event: string, callback: Function) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(callback);
}
emit(event: string, data: any) {
const callbacks = this.listeners.get(event) || [];
callbacks.forEach(cb => cb(data));
}
}
class DataFetcher {
private cache: Map<string, any> = new Map();
private eventManager: EventManager;
constructor(eventManager: EventManager) {
this.eventManager = eventManager;
// 問題:リスナーが削除されない
this.eventManager.on('refresh', () => {
this.fetchData();
});
}
async fetchData() {
const data = await fetch('/api/data').then(r => r.json());
// 問題:キャッシュが無限に増える
this.cache.set(Date.now().toString(), data);
return data;
}
}
// 使用例
const eventManager = new EventManager();
// 問題:インスタンスが作成され続ける
setInterval(() => {
new DataFetcher(eventManager);
}, 1000);
// AIによる分析と修正案:
// 1. イベントリスナーの適切な削除
// 2. キャッシュサイズの制限
// 3. WeakMapの使用でガベージコレクション対応
class EventManager {
private listeners: Map<string, Set<WeakRef<Function>>> = new Map();
on(event: string, callback: Function): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
const weakRef = new WeakRef(callback);
this.listeners.get(event)!.add(weakRef);
// クリーンアップ関数を返す
return () => {
const set = this.listeners.get(event);
if (set) {
set.delete(weakRef);
}
};
}
emit(event: string, data: any) {
const refs = this.listeners.get(event);
if (!refs) return;
const deadRefs: WeakRef<Function>[] = [];
refs.forEach(ref => {
const callback = ref.deref();
if (callback) {
callback(data);
} else {
deadRefs.push(ref);
}
});
// 死んだ参照を削除
deadRefs.forEach(ref => refs.delete(ref));
}
}
class DataFetcher {
private cache: LRUCache<string, any>;
private eventManager: EventManager;
private cleanup: (() => void) | null = null;
private abortController: AbortController | null = null;
constructor(eventManager: EventManager) {
this.eventManager = eventManager;
// LRUキャッシュで自動的に古いエントリを削除
this.cache = new LRUCache({
max: 100, // 最大100エントリ
ttl: 1000 * 60 * 5, // 5分でTTL
updateAgeOnGet: true
});
// イベントリスナーのクリーンアップ関数を保存
this.cleanup = this.eventManager.on('refresh', () => {
this.fetchData();
});
}
async fetchData() {
// 既存のリクエストをキャンセル
if (this.abortController) {
this.abortController.abort();
}
this.abortController = new AbortController();
try {
const response = await fetch('/api/data', {
signal: this.abortController.signal
});
const data = await response.json();
// キャッシュキーを意味のあるものに
const cacheKey = `data-${new Date().toISOString()}`;
this.cache.set(cacheKey, data);
return data;
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Fetch error:', error);
}
throw error;
}
}
// 重要:クリーンアップメソッド
destroy() {
if (this.cleanup) {
this.cleanup();
this.cleanup = null;
}
if (this.abortController) {
this.abortController.abort();
this.abortController = null;
}
this.cache.clear();
}
}
// 使用例(修正版)
const eventManager = new EventManager();
const fetchers = new Set<DataFetcher>();
// インスタンス管理
const intervalId = setInterval(() => {
// 古いインスタンスをクリーンアップ
if (fetchers.size >= 10) {
const oldest = fetchers.values().next().value;
oldest.destroy();
fetchers.delete(oldest);
}
const fetcher = new DataFetcher(eventManager);
fetchers.add(fetcher);
}, 1000);
// アプリケーション終了時のクリーンアップ
process.on('SIGTERM', () => {
clearInterval(intervalId);
fetchers.forEach(f => f.destroy());
process.exit(0);
});
推奨される使用方法:
注意事項:
指標 | 測定方法 | 目標値 | 効果 |
---|---|---|---|
開発速度 | ストーリーポイント/スプリント | +30% | ⭐⭐⭐⭐☆ |
コード品質 | SonarQubeスコア | 維持or向上 | ⭐⭐⭐⭐⭐ |
バグ発生率 | バグ数/リリース | -20% | ⭐⭐⭐☆☆ |
開発者満足度 | アンケート調査 | 80%以上 | ⭐⭐⭐⭐☆ |
学習効率 | 新技術習得時間 | -40% | ⭐⭐⭐⭐⭐ |
// ❌ 悪い例:曖昧で危険なプロンプト
"管理者機能を実装して。DBのパスワードは'admin123'です。"
// ✅ 良い例:明確で安全なプロンプト
"Next.js 14のApp Routerで管理者向けダッシュボードのルート保護を実装してください。
要件:
- middleware.tsでJWTトークンの検証
- 管理者ロールのチェック(roles: ['admin', 'super_admin'])
- 未認証時は/loginへリダイレクト
- 環境変数からJWT_SECRETを読み込む
- エラーハンドリングとログ出力を含める"
// ❌ 悪い例:巨大な一括生成
"ECサイト全体を作って"
// ✅ 良い例:段階的なアプローチ
"ECサイトの商品一覧ページのコンポーネントを作成してください。
まずは以下の機能から始めます:
1. 商品カードコンポーネント(画像、タイトル、価格)
2. グリッドレイアウト(レスポンシブ対応)
3. ページネーション(1ページ20件)
使用技術:React 18, TypeScript, Tailwind CSS"
AI ペアプログラミングは、正しく活用すれば開発効率と品質を大幅に向上させる強力なツールです。重要なのは、AI を「代替」ではなく「パートナー」として捉え、人間の創造性と AI の処理能力を組み合わせることです。
AI ペアプログラミングの真の価値は、コードを書く速度の向上だけでなく、開発者がより創造的で戦略的な問題に集中できるようになることにあります。
図表やUIデザインからのコード生成
プロジェクト全体を理解した提案
複数開発者とAIの同時協働
AIによる自動デバッグと修正提案