リファクタリング完全ガイド
リファクタリング完全ガイド
Section titled “リファクタリング完全ガイド”リファクタリングの実践的な手法を、実務で使える実装例とベストプラクティスとともに詳しく解説します。
1. リファクタリングとは
Section titled “1. リファクタリングとは”リファクタリングの定義
Section titled “リファクタリングの定義”リファクタリングは、コードの外部動作を変えずに内部構造を改善するプロセスです。
リファクタリングの目的 ├─ 可読性の向上 ├─ 保守性の向上 ├─ テストの容易さ └─ パフォーマンスの改善2. リファクタリングの原則
Section titled “2. リファクタリングの原則”原則1: テストを先に書く
Section titled “原則1: テストを先に書く”// リファクタリング前のテストdescribe('calculateTotal', () => { it('should calculate total correctly', () => { const order = { items: [ { price: 100, quantity: 2 }, { price: 200, quantity: 1 } ] }; expect(calculateTotal(order)).toBe(400); });});
// リファクタリング後も同じテストが通る原則2: 小さなステップで進める
Section titled “原則2: 小さなステップで進める”// ❌ 悪い例: 一度に大きな変更function processOrder(order) { // 100行のコードを一度に変更}
// ✅ 良い例: 小さなステップで変更function processOrder(order) { const items = extractItems(order); const total = calculateTotal(items); return applyDiscount(total, order.customer);}3. リファクタリングパターン
Section titled “3. リファクタリングパターン”パターン1: 関数の抽出
Section titled “パターン1: 関数の抽出”// リファクタリング前function processOrder(order: Order): number { let total = 0; for (let i = 0; i < order.items.length; i++) { total += order.items[i].price * order.items[i].quantity; } if (order.customer.type === 'VIP') { total = total * 0.9; } return total;}
// リファクタリング後function processOrder(order: Order): number { const subtotal = calculateSubtotal(order.items); return applyDiscount(subtotal, order.customer);}
function calculateSubtotal(items: OrderItem[]): number { return items.reduce((sum, item) => sum + item.price * item.quantity, 0);}
function applyDiscount(amount: number, customer: Customer): number { const discountRates = { VIP: 0.1, PREMIUM: 0.15 }; const rate = discountRates[customer.type] || 0; return amount * (1 - rate);}パターン2: 変数の抽出
Section titled “パターン2: 変数の抽出”// リファクタリング前function calculatePrice(item: Item): number { return item.basePrice * (1 + item.taxRate) * (1 - item.discountRate);}
// リファクタリング後function calculatePrice(item: Item): number { const priceWithTax = item.basePrice * (1 + item.taxRate); const finalPrice = priceWithTax * (1 - item.discountRate); return finalPrice;}パターン3: 条件式の簡略化
Section titled “パターン3: 条件式の簡略化”// リファクタリング前function getStatus(user: User): string { if (user.isActive && user.hasPermission) { return 'active'; } else { return 'inactive'; }}
// リファクタリング後function getStatus(user: User): string { return user.isActive && user.hasPermission ? 'active' : 'inactive';}パターン4: 早期リターン
Section titled “パターン4: 早期リターン”// リファクタリング前function validateUser(user: User): boolean { if (user.email) { if (user.email.includes('@')) { if (user.name) { return true; } else { return false; } } else { return false; } } else { return false; }}
// リファクタリング後function validateUser(user: User): boolean { if (!user.email || !user.email.includes('@')) { return false; } if (!user.name) { return false; } return true;}パターン5: マジックナンバーの除去
Section titled “パターン5: マジックナンバーの除去”// リファクタリング前function calculateDiscount(amount: number): number { if (amount > 1000) { return amount * 0.1; } return 0;}
// リファクタリング後const DISCOUNT_THRESHOLD = 1000;const DISCOUNT_RATE = 0.1;
function calculateDiscount(amount: number): number { if (amount > DISCOUNT_THRESHOLD) { return amount * DISCOUNT_RATE; } return 0;}4. オブジェクト指向のリファクタリング
Section titled “4. オブジェクト指向のリファクタリング”クラスの抽出
Section titled “クラスの抽出”// リファクタリング前function processOrder(order: Order): void { // 注文処理のロジック const total = calculateTotal(order.items); const discount = calculateDiscount(total, order.customer); const finalTotal = total - discount; sendEmail(order.customer.email, finalTotal);}
// リファクタリング後class OrderProcessor { calculateTotal(items: OrderItem[]): number { return items.reduce((sum, item) => sum + item.price * item.quantity, 0); }
calculateDiscount(total: number, customer: Customer): number { const discountRates = { VIP: 0.1, PREMIUM: 0.15 }; const rate = discountRates[customer.type] || 0; return total * rate; }
processOrder(order: Order): void { const total = this.calculateTotal(order.items); const discount = this.calculateDiscount(total, order.customer); const finalTotal = total - discount; this.sendEmail(order.customer.email, finalTotal); }
private sendEmail(email: string, total: number): void { // メール送信のロジック }}5. データ構造のリファクタリング
Section titled “5. データ構造のリファクタリング”配列からオブジェクトへの変換
Section titled “配列からオブジェクトへの変換”// リファクタリング前function getUserById(users: User[], id: string): User | undefined { return users.find(user => user.id === id);}
// リファクタリング後function createUserMap(users: User[]): Map<string, User> { return new Map(users.map(user => [user.id, user]));}
function getUserById(userMap: Map<string, User>, id: string): User | undefined { return userMap.get(id);}6. エラーハンドリングのリファクタリング
Section titled “6. エラーハンドリングのリファクタリング”// リファクタリング前function processPayment(amount: number): void { if (amount <= 0) { console.error('Invalid amount'); return; } if (amount > 10000) { console.error('Amount too large'); return; } // 処理}
// リファクタリング後class PaymentError extends Error { constructor(message: string) { super(message); this.name = 'PaymentError'; }}
function processPayment(amount: number): void { if (amount <= 0) { throw new PaymentError('Invalid amount'); } if (amount > 10000) { throw new PaymentError('Amount too large'); } // 処理}7. 実践的なベストプラクティス
Section titled “7. 実践的なベストプラクティス”リファクタリングのタイミング
Section titled “リファクタリングのタイミング”## リファクタリングのタイミング
1. **機能追加前**: 新しい機能を追加しやすくする2. **バグ修正後**: バグの原因となったコードを改善3. **コードレビュー時**: レビューで指摘された問題を修正4. **定期的なメンテナンス**: 技術的負債を返済リファクタリングのチェックリスト
Section titled “リファクタリングのチェックリスト”## リファクタリングチェックリスト
- [ ] テストが存在し、すべて通る- [ ] 小さなステップで進める- [ ] 外部動作が変わらないことを確認- [ ] コードレビューを受ける- [ ] ドキュメントを更新する8. よくある問題と解決方法
Section titled “8. よくある問題と解決方法”問題1: リファクタリングが大きすぎる
Section titled “問題1: リファクタリングが大きすぎる”// 解決: 小さなステップに分割// Step 1: 関数の抽出// Step 2: 変数の抽出// Step 3: 条件式の簡略化問題2: テストがない
Section titled “問題2: テストがない”// 解決: リファクタリング前にテストを書くdescribe('processOrder', () => { it('should process order correctly', () => { // テストを書く });});リファクタリング完全ガイドのポイント:
- 原則: テストを先に書く、小さなステップで進める
- パターン: 関数の抽出、変数の抽出、条件式の簡略化、早期リターン
- オブジェクト指向: クラスの抽出、責任の分離
- データ構造: 適切なデータ構造の選択
- エラーハンドリング: 適切なエラーハンドリング
- ベストプラクティス: タイミング、チェックリスト
適切なリファクタリングにより、保守性の高いコードを維持できます。