関数単位でのコード生成の重要性
現代のソフトウェア開発において、AI との協働でコードを生成する際、最も効果的なアプローチの一つが関数単位での設計と実装です。大規模で複雑なコードを一度に生成しようとすると、AI も人間も理解しにくく、バグが潜みやすいコードになりがちです。
関数単位アプローチの具体的メリット
関数単位での開発により、以下のような具体的な恩恵を得ることができます:
- 理解しやすさの向上: 各関数の役割が明確で、新しいチームメンバーでも迅速に理解可能
- テスタビリティの確保: 小さな単位でのテストが容易で、バグの特定と修正が迅速
- 再利用性の最大化: 汎用的な関数は複数のプロジェクトで活用でき、開発効率が向上
- デバッグ効率の改善: 問題の発生箇所を素早く特定し、影響範囲を限定して修正可能
- AI 生成精度の向上: 明確で限定的な要求により、Claude がより正確なコードを生成
- コードレビューの質向上: 小さな単位でのレビューにより、より詳細で建設的なフィードバックが可能
大規模システムでの関数分割戦略
エンタープライズレベルのシステムでは、適切な関数分割が特に重要になります。マイクロサービス アーキテクチャや関数型プログラミングの概念を取り入れ、システム全体を小さく独立した部品の組み合わせとして構成することで、保守性と拡張性を大幅に向上させることができます。
分割の指針
- ビジネスロジックの境界: 業務上の意味のある単位で関数を分割
- データアクセスパターン: 同じデータソースにアクセスする処理をグループ化
- 変更の頻度: 変更されやすい部分とそうでない部分を分離
- 依存関係の方向: 依存関係を一方向に保ち、循環参照を避ける
良い関数設計の原則
保守性と可読性の高い関数を設計するための原則は、長年のソフトウェア工学の実践から生まれた確立されたガイドラインです。これらの原則に従うことで、Claude との協働においても一貫して高品質なコードを生成できます。
1. 単一責任原則(Single Responsibility Principle)
一つの関数は一つのことだけを行うべきです。この原則は、関数の複雑さを制御し、理解しやすさを向上させるための基本的な考え方です。
判断基準
- 関数名で表現される動作が一つの動詞で説明できる
- 関数の説明文が「〜して、かつ〜する」のような接続詞を含まない
- 関数内のコードが同じ抽象化レベルにある
- 変更理由が複数ない(一つの要求変更で修正が必要になるのは一つの関数のみ)
実践例
// 悪い例:複数の責任を持つ関数
function processUserData(user) {
// 1. データ検証
if (!user.email || !user.name) {
throw new Error('Invalid user data');
}
// 2. データ変換
const normalizedUser = {
email: user.email.toLowerCase(),
name: user.name.trim(),
createdAt: new Date()
};
// 3. データ保存
database.users.insert(normalizedUser);
// 4. 通知送信
emailService.sendWelcomeEmail(normalizedUser.email);
return normalizedUser;
}
// 良い例:責任を分離した関数群
function validateUserData(user) {
if (!user.email || !user.name) {
throw new Error('Invalid user data');
}
}
function normalizeUserData(user) {
return {
email: user.email.toLowerCase(),
name: user.name.trim(),
createdAt: new Date()
};
}
function saveUser(user) {
return database.users.insert(user);
}
function sendWelcomeNotification(email) {
return emailService.sendWelcomeEmail(email);
}
function processUserData(user) {
validateUserData(user);
const normalizedUser = normalizeUserData(user);
const savedUser = saveUser(normalizedUser);
sendWelcomeNotification(savedUser.email);
return savedUser;
}
2. 適切な抽象化レベル
関数内のすべてのコードは同じ抽象化レベルにあるべきです。低レベルの実装詳細と高レベルのビジネスロジックを混在させると、コードの理解が困難になります。
抽象化レベルの例
- 高レベル: ビジネスロジック、ユースケースの実装
- 中レベル: データ変換、計算処理、条件分岐
- 低レベル: 文字列操作、配列操作、数値計算
// 悪い例:異なる抽象化レベルが混在
function calculateOrderTotal(order) {
// 高レベル:ビジネスロジック
let total = 0;
// 低レベル:具体的な配列操作
for (let i = 0; i < order.items.length; i++) {
total += order.items[i].price * order.items[i].quantity;
}
// 中レベル:割引計算
if (order.customer.membership === 'premium') {
total *= 0.9;
}
// 低レベル:小数点処理
return Math.round(total * 100) / 100;
}
// 良い例:同じ抽象化レベルで統一
function calculateOrderTotal(order) {
const subtotal = calculateSubtotal(order.items);
const discount = calculateDiscount(subtotal, order.customer);
return formatCurrency(subtotal - discount);
}
function calculateSubtotal(items) {
return items.reduce((total, item) =>
total + (item.price * item.quantity), 0);
}
function calculateDiscount(subtotal, customer) {
const discountRate = getDiscountRate(customer.membership);
return subtotal * discountRate;
}
function getDiscountRate(membership) {
const rates = {
'premium': 0.1,
'gold': 0.05,
'regular': 0
};
return rates[membership] || 0;
}
function formatCurrency(amount) {
return Math.round(amount * 100) / 100;
}
3. 明確な入出力設計
関数の引数と戻り値は明確で予測可能であるべきです。副作用は最小限に抑え、できる限り純粋関数を目指します。
純粋関数の利点
- テストしやすさ: 同じ入力に対して常に同じ出力が得られる
- 並行処理安全性: 状態を変更しないため、並行実行でも安全
- キャッシュ可能性: 結果をメモ化して性能向上が可能
- 推論しやすさ: 関数の動作を予測しやすい
// 悪い例:副作用がある関数
let globalCounter = 0;
function processItem(item) {
// 外部状態を変更(副作用)
globalCounter++;
// DOM を直接操作(副作用)
document.getElementById('status').innerText = `Processing item ${globalCounter}`;
// ログ出力(副作用)
console.log(`Processing: ${item.name}`);
return {
...item,
processed: true,
processedAt: new Date()
};
}
// 良い例:純粋関数
function processItem(item, timestamp = Date.now()) {
return {
...item,
processed: true,
processedAt: new Date(timestamp)
};
}
// 副作用は別の関数で管理
function processItemWithEffects(item, counter, logger, statusUpdater) {
const newCounter = counter + 1;
const processedItem = processItem(item);
logger.log(`Processing: ${item.name}`);
statusUpdater.update(`Processing item ${newCounter}`);
return {
processedItem,
newCounter
};
}
4. 適切なサイズの維持
関数は理想的には 10-20 行以内、最大でも 50 行程度に収めます。それ以上になる場合は、より小さな関数への分割を検討します。
サイズ制限の理由
- 認知負荷の軽減: 一度に理解できる情報量には限界がある
- 画面表示の最適化: 一画面で全体を把握できる
- テストの容易さ: 小さな関数の方がテストケースを網羅しやすい
- デバッグの効率性: 問題の所在を特定しやすい
悪い例 vs 良い例の詳細比較
実際のコード例を通じて、良い関数設計と悪い関数設計の違いを具体的に理解していきましょう。
例1:ECサイトの注文処理システム
悪い例:すべてを一つの関数で処理
function processOrder(orderData) {
// 検証処理
if (!orderData || !orderData.items || orderData.items.length === 0) {
throw new Error('Invalid order data');
}
let total = 0;
let validItems = [];
// 商品検証と価格計算
for (let i = 0; i < orderData.items.length; i++) {
const item = orderData.items[i];
// 在庫確認
const stock = getItemStock(item.id);
if (stock < item.quantity) {
throw new Error(`Insufficient stock for item ${item.id}`);
}
// 価格検証
const currentPrice = getItemPrice(item.id);
if (currentPrice !== item.price) {
throw new Error(`Price mismatch for item ${item.id}`);
}
validItems.push(item);
total += item.price * item.quantity;
}
// 割引計算
let discount = 0;
if (orderData.customer.membership === 'premium') {
discount = total * 0.15;
} else if (orderData.customer.membership === 'gold') {
discount = total * 0.10;
} else if (total > 10000) {
discount = total * 0.05;
}
// 送料計算
let shipping = 0;
if (total - discount < 5000) {
shipping = 500;
}
const finalTotal = total - discount + shipping;
// 在庫更新
for (let i = 0; i < validItems.length; i++) {
updateStock(validItems[i].id, validItems[i].quantity);
}
// 注文データ保存
const order = {
id: generateOrderId(),
customerId: orderData.customer.id,
items: validItems,
subtotal: total,
discount: discount,
shipping: shipping,
total: finalTotal,
status: 'pending',
createdAt: new Date()
};
saveOrder(order);
// 通知送信
sendOrderConfirmation(orderData.customer.email, order);
return order;
}
良い例:責任ごとに関数を分割
// メイン処理:高レベルなビジネスフロー
function processOrder(orderData) {
validateOrderData(orderData);
const validatedItems = validateAndPrepareItems(orderData.items);
const pricing = calculatePricing(validatedItems, orderData.customer);
const order = createOrder(orderData, validatedItems, pricing);
updateInventory(validatedItems);
const savedOrder = saveOrder(order);
sendOrderConfirmation(orderData.customer.email, savedOrder);
return savedOrder;
}
// バリデーション関数群
function validateOrderData(orderData) {
if (!orderData || !orderData.items || orderData.items.length === 0) {
throw new Error('Invalid order data');
}
if (!orderData.customer || !orderData.customer.id) {
throw new Error('Invalid customer data');
}
}
function validateAndPrepareItems(items) {
return items.map(item => {
validateItemStock(item);
validateItemPrice(item);
return item;
});
}
function validateItemStock(item) {
const stock = getItemStock(item.id);
if (stock < item.quantity) {
throw new Error(`Insufficient stock for item ${item.id}`);
}
}
function validateItemPrice(item) {
const currentPrice = getItemPrice(item.id);
if (currentPrice !== item.price) {
throw new Error(`Price mismatch for item ${item.id}`);
}
}
// 価格計算関数群
function calculatePricing(items, customer) {
const subtotal = calculateSubtotal(items);
const discount = calculateDiscount(subtotal, customer);
const shipping = calculateShipping(subtotal - discount);
return {
subtotal,
discount,
shipping,
total: subtotal - discount + shipping
};
}
function calculateSubtotal(items) {
return items.reduce((total, item) =>
total + (item.price * item.quantity), 0);
}
function calculateDiscount(subtotal, customer) {
const membershipDiscount = getMembershipDiscount(customer.membership, subtotal);
const volumeDiscount = getVolumeDiscount(subtotal);
return Math.max(membershipDiscount, volumeDiscount);
}
function getMembershipDiscount(membership, subtotal) {
const rates = {
'premium': 0.15,
'gold': 0.10,
'regular': 0
};
return subtotal * (rates[membership] || 0);
}
function getVolumeDiscount(subtotal) {
return subtotal > 10000 ? subtotal * 0.05 : 0;
}
function calculateShipping(amount) {
return amount < 5000 ? 500 : 0;
}
// 注文作成関数
function createOrder(orderData, items, pricing) {
return {
id: generateOrderId(),
customerId: orderData.customer.id,
items: items,
subtotal: pricing.subtotal,
discount: pricing.discount,
shipping: pricing.shipping,
total: pricing.total,
status: 'pending',
createdAt: new Date()
};
}
// 在庫更新関数
function updateInventory(items) {
items.forEach(item => {
updateStock(item.id, item.quantity);
});
}
改善効果の比較
観点 |
悪い例 |
良い例 |
改善効果 |
関数の行数 |
85 行 |
15 行以下 × 複数 |
理解しやすさ向上 |
循環的複雑度 |
12 |
2-4 |
バグ発生率低下 |
テスト可能性 |
困難 |
容易 |
品質保証向上 |
再利用性 |
不可 |
高い |
開発効率向上 |
変更容易性 |
困難 |
容易 |
保守コスト削減 |
関数の構造設計
優秀な関数は一定の構造パターンに従っています。この構造を意識してClaude にコード生成を依頼することで、より質の高い実装を得ることができます。
理想的な関数の構造テンプレート
1. 関数シグネチャ
明確な関数名と型定義された引数により、関数の意図を表現します。
// TypeScript での型安全な関数シグネチャ
function calculateTotalPrice(
items: CartItem[],
discount: number,
taxRate: number = 0.1
): PriceBreakdown {
// 実装
}
// JSDoc を使用した JavaScript
/**
* カート内商品の合計金額を計算します
* @param {CartItem[]} items - カート内の商品配列
* @param {number} discount - 割引率(0-1の範囲)
* @param {number} [taxRate=0.1] - 税率(デフォルト10%)
* @returns {PriceBreakdown} 価格の詳細内訳
*/
function calculateTotalPrice(items, discount, taxRate = 0.1) {
// 実装
}
2. 入力検証(ガード節)
早期リターンを使用して異常系を最初に処理し、メインロジックを単純化します。
function calculateTotalPrice(items, discount, taxRate = 0.1) {
// 入力検証:早期リターンで異常系を処理
if (!items || !Array.isArray(items)) {
throw new Error('Items must be a valid array');
}
if (items.length === 0) {
return { subtotal: 0, tax: 0, total: 0, discount: 0 };
}
if (discount < 0 || discount > 1) {
throw new Error('Discount must be between 0 and 1');
}
if (taxRate < 0 || taxRate > 1) {
throw new Error('Tax rate must be between 0 and 1');
}
// メインロジックに進む
// ...
}
3. メインロジック
単一の責任を持つ処理を、明確で理解しやすい形で実装します。
function calculateTotalPrice(items, discount, taxRate = 0.1) {
// ... 入力検証
// メインロジック:小さなステップに分割
const subtotal = items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0);
const discountAmount = subtotal * discount;
const subtotalAfterDiscount = subtotal - discountAmount;
const taxAmount = subtotalAfterDiscount * taxRate;
const total = subtotalAfterDiscount + taxAmount;
// 計算結果の返却
return {
subtotal: formatCurrency(subtotal),
discount: formatCurrency(discountAmount),
tax: formatCurrency(taxAmount),
total: formatCurrency(total)
};
}
function formatCurrency(amount) {
return Math.round(amount * 100) / 100;
}
4. 戻り値の設計
予測可能で一貫性のある出力形式を設計します。
// 良い例:構造化された戻り値
interface PriceBreakdown {
subtotal: number;
discount: number;
tax: number;
total: number;
currency?: string;
appliedDiscounts?: string[];
}
// さらに良い例:計算詳細も含む
interface DetailedPriceBreakdown extends PriceBreakdown {
items: Array<{
id: string;
name: string;
price: number;
quantity: number;
lineTotal: number;
}>;
calculations: {
discountRate: number;
taxRate: number;
calculatedAt: Date;
};
}
プロンプトパターンとテンプレート
Claude との効果的な協働には、目的に応じた適切なプロンプトパターンの使用が重要です。以下に、関数生成でよく使用される実証済みのパターンを紹介します。
基本的なプロンプトテンプレート
1. フィルタリング関数のパターン
【プロンプト例】
「配列から特定の条件を満たす要素だけを抽出する関数を作成してください。
関数仕様:
- 関数名: filterActiveUsers
- 入力: User[] (ユーザー配列)
- 条件: status が 'active' で、lastLoginDate が過去30日以内
- 出力: User[] (条件を満たすユーザーのみ)
- 言語: TypeScript
- エラーハンドリング: 入力値の検証を含める
- パフォーマンス: 大量データ(10万件以上)でも効率的に動作
テストケース:
- 空配列の場合
- 条件を満たすユーザーがいない場合
- 全員が条件を満たす場合
- null/undefined の処理」
2. 変換関数のパターン
【プロンプト例】
「APIレスポンスをフロントエンド用のデータ形式に変換する関数を作成してください。
変換仕様:
- 関数名: transformUserApiResponse
- 入力: RawUserData (APIからの生データ)
- 出力: UserDisplayData (画面表示用データ)
変換ルール:
- user_name → displayName(スネークケース → キャメルケース)
- birth_date → age(日付 → 年齢計算)
- profile_image_url → avatarUrl(null の場合はデフォルト画像URL)
- created_at → joinDate(ISO文字列 → Date オブジェクト)
要件:
- TypeScript で型安全に実装
- 不正な日付形式の場合のエラーハンドリング
- JSDoc コメントでドキュメント化
- 純粋関数として実装(副作用なし)」
3. 集計関数のパターン
【プロンプト例】
「売上データから統計情報を計算する関数を作成してください。
集計仕様:
- 関数名: calculateSalesStatistics
- 入力: Sale[] (売上記録の配列)
- 出力: SalesStatistics (統計結果オブジェクト)
計算項目:
- 合計売上金額
- 平均売上金額
- 最大・最小売上金額
- 売上件数
- 月別売上サマリー
- 商品カテゴリ別売上
制約条件:
- 大量データ(100万件)でも高速動作
- メモリ使用量の最適化
- 不正データ(負の金額等)のスキップ
- 精度の保持(小数点計算の誤差対策)
拡張性:
- 新しい集計項目を簡単に追加できる設計
- フィルタリング条件を後から適用可能」
4. 検証関数のパターン
【プロンプト例】
「フォーム入力データの包括的な検証関数を作成してください。
検証仕様:
- 関数名: validateUserRegistrationForm
- 入力: UserRegistrationData
- 出力: ValidationResult(成功/失敗と詳細メッセージ)
検証ルール:
- email: RFC5322準拠、重複チェック
- password: 8文字以上、大小英字・数字・記号を含む
- name: 1-50文字、特殊文字制限
- age: 13-120歳の範囲
- phone: 国際電話番号形式に対応
要件:
- 段階的検証(早期終了せず全項目チェック)
- 国際化対応(多言語エラーメッセージ)
- セキュリティ考慮(XSS、SQLインジェクション対策)
- カスタム検証ルールの追加可能性
- パフォーマンス(正規表現の最適化)」
高度なプロンプトパターン
5. 状態管理関数のパターン
【プロンプト例】
「ワークフローの状態遷移を管理する関数群を作成してください。
状態設計:
- 状態: draft → review → approved → published → archived
- 各状態での可能な操作と制約を定義
- 権限に基づく操作制限
- 状態変更時のコールバック処理
関数群:
1. getAvailableTransitions(currentState, userRole)
2. canTransition(fromState, toState, userRole)
3. executeTransition(item, toState, user, metadata)
4. validateTransitionData(transitionData)
要件:
- Immutable な状態更新
- ロールベースアクセス制御
- 詳細な監査ログ
- 楽観的ロック機能
- 状態遷移の履歴管理」
6. 非同期処理パターン
【プロンプト例】
「外部APIを呼び出し、レスポンス処理を行う堅牢な非同期関数を作成してください。
関数仕様:
- 関数名: fetchUserDataWithRetry
- 外部API: REST API (JSON レスポンス)
- 復旧戦略: 指数バックオフによるリトライ
- タイムアウト: 30秒
- キャッシュ: メモリキャッシュ(5分間)
エラーハンドリング:
- ネットワークエラー: 3回リトライ
- HTTP 4xx: リトライしない、エラー詳細を返却
- HTTP 5xx: 5回リトライ
- タイムアウト: 2回リトライ
- レスポンス形式エラー: バリデーション失敗時の処理
パフォーマンス最適化:
- 並行リクエスト制限
- AbortController によるキャンセル機能
- メモリリーク防止
- リクエスト重複排除」
関数生成のワークフロー
効率的で高品質な関数生成には、体系的なワークフローの適用が重要です。以下のプロセスに従うことで、一貫して優れた結果を得ることができます。
フェーズ1:要件定義と設計
1-1. 機能要件の明確化
- 目的の定義: 関数が解決すべき問題を明確に記述
- 入力の仕様: 引数の型、制約、デフォルト値を定義
- 出力の仕様: 戻り値の形式、期待される結果を明示
- 副作用の特定: ファイル操作、ネットワーク通信等の外部依存を列挙
1-2. 非機能要件の検討
- パフォーマンス: 実行時間、メモリ使用量の要求
- スケーラビリティ: 大量データ処理への対応
- セキュリティ: 入力値検証、権限チェック
- エラーハンドリング: 異常系の処理方針
- 保守性: 将来の拡張・変更への対応
【要件定義例】
機能要件:
- ユーザーの注文履歴から推奨商品を生成
- 入力: userId (string), limit (number, デフォルト5)
- 出力: RecommendedProduct[]
- 考慮要素: 購入履歴、カテゴリ preferences、在庫状況
非機能要件:
- レスポンス時間: 500ms 以内
- 対象データ: 最大10万件の注文履歴
- 更新頻度: リアルタイム在庫反映
- セキュリティ: ユーザー権限チェック必須
- 拡張性: 新しい推奨アルゴリズム追加可能
フェーズ2:インターフェース設計
実装前に関数のインターフェースを設計することで、後の変更コストを削減できます。
// TypeScript でのインターフェース設計例
interface UserRecommendationOptions {
userId: string;
limit?: number;
categories?: string[];
priceRange?: {
min: number;
max: number;
};
excludeRecentPurchases?: boolean;
}
interface RecommendedProduct {
id: string;
name: string;
price: number;
category: string;
score: number; // 推奨度 0-1
reason: 'purchase_history' | 'category_preference' | 'trending';
}
interface RecommendationResult {
products: RecommendedProduct[];
totalCount: number;
generatedAt: Date;
algorithm: string;
}
// 関数シグネチャ
async function generateUserRecommendations(
options: UserRecommendationOptions
): Promise
フェーズ3:テスト駆動設計
実装前にテストケースを作成することで、要求仕様を明確化し、実装の品質を向上させます。
// テストケース例(Jest)
describe('generateUserRecommendations', () => {
test('基本的な推奨商品生成', async () => {
const options = { userId: 'user123', limit: 3 };
const result = await generateUserRecommendations(options);
expect(result.products).toHaveLength(3);
expect(result.products[0]).toHaveProperty('score');
expect(result.totalCount).toBeGreaterThanOrEqual(3);
});
test('存在しないユーザーID', async () => {
const options = { userId: 'invalid-user' };
await expect(generateUserRecommendations(options))
.rejects.toThrow('User not found');
});
test('カテゴリフィルタリング', async () => {
const options = {
userId: 'user123',
categories: ['electronics', 'books']
};
const result = await generateUserRecommendations(options);
result.products.forEach(product => {
expect(['electronics', 'books']).toContain(product.category);
});
});
test('価格範囲フィルタリング', async () => {
const options = {
userId: 'user123',
priceRange: { min: 1000, max: 5000 }
};
const result = await generateUserRecommendations(options);
result.products.forEach(product => {
expect(product.price).toBeGreaterThanOrEqual(1000);
expect(product.price).toBeLessThanOrEqual(5000);
});
});
});
フェーズ4:Claude による実装生成
テストケースと詳細な要件を元に、Claude に実装を依頼します。
【実装依頼プロンプト】
「以下の要件とテストケースを満たす関数を実装してください。
[事前に定義したインターフェース]
[テストケース]
実装要件:
- TypeScript で型安全に実装
- エラーハンドリングを適切に行う
- パフォーマンスを考慮した効率的なアルゴリズム
- JSDoc でドキュメント化
- 単体テストで100%カバレッジを達成可能な設計
技術的制約:
- データベース: PostgreSQL使用
- ORM: Prisma使用
- キャッシュ: Redis使用
- ログ: Winston使用
参考情報:
- 既存の関数: getUserPurchaseHistory(), getProductDetails()
- データベーススキーマ: [スキーマ情報]」
フェーズ5:レビューと最適化
生成された実装を評価し、必要に応じて改善を依頼します。
レビューチェックポイント
- 機能的正確性: 要件を正しく満たしているか
- エッジケース対応: 異常系が適切に処理されているか
- パフォーマンス: 効率的なアルゴリズムが使用されているか
- セキュリティ: 入力検証やアクセス制御が適切か
- 保守性: 理解しやすく、変更しやすいコードか
- テスタビリティ: 単体テストが書きやすい設計か
【改善依頼プロンプト例】
「実装を確認しました。以下の点で改善をお願いします:
1. パフォーマンス最適化
- データベースクエリを1回にまとめる
- 不要な配列コピーを避ける
2. エラーハンドリング強化
- より具体的なエラーメッセージ
- エラーログの追加
3. 型安全性向上
- より厳密な型定義
- runtime での型チェック追加」
実践例と応用技術
実際のプロジェクトでの関数生成例を通じて、理論を実践に応用する方法を学びましょう。
例1:認証システムの構築
システム全体の設計
ユーザー認証システムを関数単位で段階的に構築していきます。
ステップ1:基本的なパスワードハッシュ化
【Claude への依頼】
「セキュアなパスワードハッシュ化関数を作成してください。
要件:
- bcrypt を使用
- ソルトラウンド: 12
- 入力検証(最小8文字、最大128文字)
- TypeScript で実装
- エラーハンドリング込み」
【生成された関数】
import bcrypt from 'bcrypt';
/**
* パスワードを安全にハッシュ化
* @param plainPassword - プレーンテキストパスワード
* @returns ハッシュ化されたパスワード
*/
export async function hashPassword(plainPassword: string): Promise {
if (typeof plainPassword !== 'string') {
throw new Error('Password must be a string');
}
if (plainPassword.length < 8) {
throw new Error('Password must be at least 8 characters long');
}
if (plainPassword.length > 128) {
throw new Error('Password must be no more than 128 characters long');
}
try {
const saltRounds = 12;
return await bcrypt.hash(plainPassword, saltRounds);
} catch (error) {
throw new Error(`Password hashing failed: ${error.message}`);
}
}
ステップ2:パスワード検証関数
【Claude への依頼】
「ハッシュ化されたパスワードと平文パスワードを比較する関数を作成してください。」
【生成された関数】
/**
* パスワードを検証
* @param plainPassword - 入力されたプレーンテキストパスワード
* @param hashedPassword - データベースに保存されたハッシュ
* @returns パスワードが一致するかどうか
*/
export async function verifyPassword(
plainPassword: string,
hashedPassword: string
): Promise {
if (!plainPassword || !hashedPassword) {
return false;
}
try {
return await bcrypt.compare(plainPassword, hashedPassword);
} catch (error) {
console.error('Password verification error:', error);
return false;
}
}
ステップ3:JWT トークン生成
【Claude への依頼】
「ユーザー情報からJWTトークンを生成する関数を作成してください。」
【生成された関数】
import jwt from 'jsonwebtoken';
interface TokenPayload {
userId: string;
email: string;
role: string;
}
interface TokenOptions {
expiresIn?: string;
issuer?: string;
}
/**
* JWT トークンを生成
* @param payload - トークンに含めるユーザー情報
* @param options - トークンオプション
* @returns 生成されたJWTトークン
*/
export function generateToken(
payload: TokenPayload,
options: TokenOptions = {}
): string {
const secret = process.env.JWT_SECRET;
if (!secret) {
throw new Error('JWT_SECRET environment variable is required');
}
const defaultOptions = {
expiresIn: '24h',
issuer: 'your-app-name',
...options
};
try {
return jwt.sign(payload, secret, defaultOptions);
} catch (error) {
throw new Error(`Token generation failed: ${error.message}`);
}
}
ステップ4:統合認証関数
【Claude への依頼】
「これまでの関数を組み合わせて、ログイン処理を行う関数を作成してください。」
【生成された関数】
interface LoginCredentials {
email: string;
password: string;
}
interface LoginResult {
success: boolean;
token?: string;
user?: {
id: string;
email: string;
name: string;
role: string;
};
error?: string;
}
/**
* ユーザーログイン処理
* @param credentials - ログイン認証情報
* @param userRepository - ユーザーデータアクセス
* @returns ログイン結果
*/
export async function authenticateUser(
credentials: LoginCredentials,
userRepository: UserRepository
): Promise {
try {
// 入力検証
if (!credentials.email || !credentials.password) {
return {
success: false,
error: 'Email and password are required'
};
}
// ユーザー検索
const user = await userRepository.findByEmail(credentials.email);
if (!user) {
return {
success: false,
error: 'Invalid credentials'
};
}
// パスワード検証
const isValidPassword = await verifyPassword(
credentials.password,
user.passwordHash
);
if (!isValidPassword) {
return {
success: false,
error: 'Invalid credentials'
};
}
// トークン生成
const token = generateToken({
userId: user.id,
email: user.email,
role: user.role
});
return {
success: true,
token,
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role
}
};
} catch (error) {
return {
success: false,
error: 'Authentication failed due to server error'
};
}
}
例2:データ処理パイプラインの構築
CSV ファイル処理システム
大量のCSVデータを効率的に処理するパイプラインを構築します。
【段階的な関数生成】
// ステップ1: CSVファイル読み込み
async function readCsvFile(filePath: string): Promise
// ステップ2: データ検証
function validateCsvRow(row: string[], schema: ValidationSchema): ValidationResult
// ステップ3: データ変換
function transformCsvRow(row: string[], mapping: FieldMapping): Record
// ステップ4: バッチ処理
async function processCsvBatch(
rows: string[][],
batchSize: number,
processor: (batch: any[]) => Promise
): Promise
// ステップ5: 統合パイプライン
async function processCsvFile(
filePath: string,
config: ProcessingConfig
): Promise
品質評価と改善
生成された関数の品質を客観的に評価し、継続的に改善していくための指標とプロセスを確立することが重要です。
定量的品質指標
指標 |
優秀 |
良好 |
要改善 |
測定方法 |
関数の行数 |
< 15行 |
15-30行 |
> 30行 |
コード行数カウント |
循環的複雑度 |
< 3 |
3-7 |
> 7 |
ESLint complexity |
引数の数 |
1-2個 |
3-4個 |
> 4個 |
関数シグネチャ分析 |
ネストレベル |
< 2 |
2-3 |
> 3 |
静的解析ツール |
テストカバレッジ |
> 95% |
80-95% |
< 80% |
Jest/Istanbul |
型安全性 |
100% |
90-99% |
< 90% |
TypeScript strict |
定性的品質評価
コードレビューチェックリスト
- 可読性: 関数名と実装が一致しているか
- 一貫性: プロジェクト全体のコーディング規約に従っているか
- 拡張性: 将来の機能追加に対応しやすい設計か
- 効率性: 不要な計算や処理が含まれていないか
- 安全性: セキュリティホールや脆弱性がないか
継続的改善プロセス
1. 自動品質チェック
// package.json の scripts
{
"scripts": {
"lint": "eslint src --ext .ts,.js",
"type-check": "tsc --noEmit",
"test": "jest --coverage",
"quality-check": "npm run lint && npm run type-check && npm run test",
"complexity": "npx complexity-report src/"
}
}
// .eslintrc.js の complexity 設定
module.exports = {
rules: {
"complexity": ["error", { "max": 5 }],
"max-lines-per-function": ["error", { "max": 20 }],
"max-params": ["error", { "max": 3 }],
"max-depth": ["error", { "max": 3 }]
}
};
2. 定期的なリファクタリング
品質指標が基準を下回る関数を特定し、計画的にリファクタリングを実施します。
【リファクタリング依頼プロンプト】
「以下の関数の品質を改善してください。
現在の問題点:
- 循環的複雑度: 12(目標: 5以下)
- 行数: 85行(目標: 20行以下)
- 引数の数: 7個(目標: 3個以下)
改善要求:
1. 複雑な条件分岐の簡素化
2. 長い関数の分割
3. 引数のオブジェクト化
4. 責任の分離
制約:
- 既存のAPIインターフェースは維持
- パフォーマンスは同等以上
- 既存テストは全てパス」
高度な関数設計パターン
基本的な関数設計を習得した後は、より高度で専門的なパターンを学ぶことで、複雑な要求にも対応できるようになります。
関数型プログラミングパターン
高階関数の活用
// 汎用的なフィルタリング関数生成器
function createFilterFunction(predicate: (item: T) => boolean) {
return (items: T[]): T[] => items.filter(predicate);
}
// 使用例
const filterActiveUsers = createFilterFunction(user => user.isActive);
const filterRecentOrders = createFilterFunction(
order => Date.now() - order.createdAt.getTime() < 86400000
);
// Claude への依頼例
「以下の要件で高階関数を作成してください:
- 関数名: createSortFunction
- 機能: ソート関数を生成する関数
- 引数: ソートキーとソート方向を指定
- 戻り値: 配列をソートする関数
- TypeScript で型安全に実装」
カリー化(部分適用)パターン
// カリー化された計算関数
const createCalculator = (operation: string) =>
(a: number) =>
(b: number): number => {
switch (operation) {
case 'add': return a + b;
case 'multiply': return a * b;
case 'subtract': return a - b;
default: throw new Error('Unknown operation');
}
};
// 使用例
const add = createCalculator('add');
const add10 = add(10);
const result = add10(5); // 15
// Claude への依頼例
「税金計算のためのカリー化関数を作成してください:
- 税率を最初に設定
- 商品価格を後から適用
- 割引率も考慮できる設計
- TypeScript で型安全に実装」
非同期プログラミングパターン
Promise チェーンと並行処理
【Claude への依頼】
「複数の非同期処理を効率的に実行する関数群を作成してください。
要件:
1. 順次実行: processSequentially(tasks)
2. 並行実行: processInParallel(tasks, concurrency)
3. 部分失敗許容: processWithFailover(tasks, maxFailures)
4. タイムアウト対応: processWithTimeout(tasks, timeout)
5. プログレス通知: processWithProgress(tasks, onProgress)
各関数は:
- TypeScript で型安全
- エラーハンドリング込み
- キャンセル機能付き
- 詳細なログ出力」
生成された例
interface Task {
id: string;
execute: () => Promise;
}
interface TaskResult {
id: string;
status: 'success' | 'error';
result?: T;
error?: Error;
duration: number;
}
/**
* タスクを順次実行
*/
async function processSequentially(
tasks: Task[],
onProgress?: (completed: number, total: number) => void
): Promise[]> {
const results: TaskResult[] = [];
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i];
const startTime = Date.now();
try {
const result = await task.execute();
results.push({
id: task.id,
status: 'success',
result,
duration: Date.now() - startTime
});
} catch (error) {
results.push({
id: task.id,
status: 'error',
error: error as Error,
duration: Date.now() - startTime
});
}
onProgress?.(i + 1, tasks.length);
}
return results;
}
/**
* 制限付き並行実行
*/
async function processInParallel(
tasks: Task[],
concurrency: number = 3
): Promise[]> {
const results: TaskResult[] = [];
const executing: Promise[] = [];
for (const task of tasks) {
const promise = executeTask(task).then(result => {
results.push(result);
});
executing.push(promise);
if (executing.length >= concurrency) {
await Promise.race(executing);
executing.splice(executing.findIndex(p => p === promise), 1);
}
}
await Promise.all(executing);
return results;
}
async function executeTask(task: Task): Promise> {
const startTime = Date.now();
try {
const result = await task.execute();
return {
id: task.id,
status: 'success',
result,
duration: Date.now() - startTime
};
} catch (error) {
return {
id: task.id,
status: 'error',
error: error as Error,
duration: Date.now() - startTime
};
}
}
デザインパターンの適用
ファクトリーパターン
【Claude への依頼】
「ログ出力機能のファクトリーパターンを実装してください。
要件:
- 環境(dev/staging/prod)に応じて異なるログ実装を生成
- インターフェース: Logger with log(), error(), warn()
- dev: Console + ファイル出力
- staging: ファイル + 外部サービス
- prod: 外部サービス + アラート機能
- 設定ベースで動作を制御可能」
戦略パターン
【Claude への依頼】
「支払い処理の戦略パターンを実装してください。
支払い方法:
- クレジットカード: Stripe API
- 銀行振込: 銀行API
- デジタルウォレット: PayPal API
- ポイント: 内部ポイントシステム
共通インターフェース:
- processPayment(amount, details)
- validatePayment(details)
- getTransactionFee(amount)
- rollbackPayment(transactionId)
各戦略は独立してテスト可能な設計」
まとめ
関数単位でのコード生成は、AI 協調プログラミングにおける最も重要で効果的な手法の一つです。適切な原則に従い、体系的なアプローチを取ることで、保守性が高く、テスト可能で、拡張しやすいコードを効率的に生成できます。
成功のための重要ポイント
- 設計優先: 実装前の要件定義とインターフェース設計の重要性
- 単一責任: 各関数が明確で限定的な責任を持つこと
- テスト駆動: テストケースを先に作成し、それを満たす実装を生成
- 段階的改善: 基本機能から始めて、段階的に最適化を図る
- 品質継続: 定量的指標に基づく継続的な品質向上
AI 協働の価値
Claude との協働により、従来の手動コーディングでは実現困難だった一貫性、効率性、品質を同時に達成できます。適切なプロンプト設計と段階的なアプローチにより、経験豊富な開発者と同等、あるいはそれ以上の品質のコードを生成することが可能です。
重要なのは、AI を単なるコード生成ツールとして使うのではなく、設計パートナーとして活用することです。明確な要求と適切なフィードバックを通じて、より良いソフトウェアを共同で創造していきましょう。
この記事で紹介した技法を実践し、プロジェクトの特性に応じてカスタマイズすることで、関数単位でのコード生成を最大限活用できるようになります。継続的な学習と改善により、AI 協調プログラミングのスキルを向上させていってください。