Skip to content

リファクタリングガイド

この包括的なガイドでは、Traeでコード品質と保守性を向上させるためのリファクタリング技術、ツール、ベストプラクティスについて説明します。

リファクタリングの概要

リファクタリングとは?

リファクタリングとは、外部動作を変更することなく既存のコードを再構築するプロセスです。目標は、機能を保持しながらコードの可読性を向上させ、複雑さを軽減し、保守性を高めることです。

リファクタリングの利点

  • コード品質の向上: より清潔で読みやすいコード
  • 保守性の向上: 変更や拡張が容易
  • 技術的負債の削減: コードの臭いやアンチパターンの排除
  • パフォーマンスの向上: 最適化されたアルゴリズムとデータ構造
  • 開発者の生産性向上: 理解しやすく作業しやすい

リファクタリングのタイミング

  • 新機能を追加する前
  • バグを修正する際
  • コードレビュー中
  • コードが理解困難になった時
  • パフォーマンス問題が発生した時
  • 定期的なメンテナンスの一環として

Traeのリファクタリングツール

組み込みリファクタリング機能

シンボルの名前変更

typescript
// リファクタリング前
function calculateTotal(items: Item[]): number {
    let sum = 0;
    for (const item of items) {
        sum += item.price;
    }
    return sum;
}

// 'sum'を'total'に名前変更後
function calculateTotal(items: Item[]): number {
    let total = 0;
    for (const item of items) {
        total += item.price;
    }
    return total;
}

使用方法:

  1. シンボルを右クリック → "シンボルの名前変更" (F2)
  2. 新しい名前を入力
  3. Enterキーを押してすべてのファイルに変更を適用

メソッドの抽出

typescript
// 抽出前
class OrderProcessor {
    processOrder(order: Order): void {
        // 注文の検証
        if (!order.items || order.items.length === 0) {
            throw new Error('注文にはアイテムが必要です');
        }
        if (!order.customer) {
            throw new Error('注文には顧客が必要です');
        }
        
        // 合計の計算
        let total = 0;
        for (const item of order.items) {
            total += item.price * item.quantity;
        }
        order.total = total;
        
        // 注文の保存
        this.saveOrder(order);
    }
}

// メソッド抽出後
class OrderProcessor {
    processOrder(order: Order): void {
        this.validateOrder(order);
        this.calculateTotal(order);
        this.saveOrder(order);
    }
    
    private validateOrder(order: Order): void {
        if (!order.items || order.items.length === 0) {
            throw new Error('注文にはアイテムが必要です');
        }
        if (!order.customer) {
            throw new Error('注文には顧客が必要です');
        }
    }
    
    private calculateTotal(order: Order): void {
        let total = 0;
        for (const item of order.items) {
            total += item.price * item.quantity;
        }
        order.total = total;
    }
    
    private saveOrder(order: Order): void {
        // 注文保存ロジック
    }
}

使用方法:

  1. 抽出したいコードを選択
  2. 右クリック → "メソッドの抽出" (Ctrl+Shift+M)
  3. メソッド名を入力
  4. Enterキーを押して適用

変数の抽出

typescript
// 抽出前
function calculateDiscount(order: Order): number {
    return order.total * 0.1 + (order.items.length > 5 ? order.total * 0.05 : 0);
}

// 変数抽出後
function calculateDiscount(order: Order): number {
    const baseDiscount = order.total * 0.1;
    const bulkDiscount = order.items.length > 5 ? order.total * 0.05 : 0;
    return baseDiscount + bulkDiscount;
}

インターフェースの抽出

typescript
// 抽出前
class EmailService {
    sendEmail(to: string, subject: string, body: string): void {
        // メール送信ロジック
    }
    
    sendBulkEmail(recipients: string[], subject: string, body: string): void {
        // 一括メール送信ロジック
    }
}

// インターフェース抽出後
interface IEmailService {
    sendEmail(to: string, subject: string, body: string): void;
    sendBulkEmail(recipients: string[], subject: string, body: string): void;
}

class EmailService implements IEmailService {
    sendEmail(to: string, subject: string, body: string): void {
        // メール送信ロジック
    }
    
    sendBulkEmail(recipients: string[], subject: string, body: string): void {
        // 一括メール送信ロジック
    }
}

高度なリファクタリング機能

クラスの移動

typescript
// 移動前 - utils/helpers.ts
export class ValidationHelper {
    static validateEmail(email: string): boolean {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }
}

// 移動後 - validators/emailValidator.ts
export class EmailValidator {
    static validate(email: string): boolean {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }
}

ファイルの移動とリネーム

typescript
// 移動前のファイル構造
src/
  utils/
    stringHelpers.ts
    dateHelpers.ts
    
// 移動後のファイル構造
src/
  helpers/
    string/
      stringUtils.ts
    date/
      dateUtils.ts

リファクタリングパターン

1. 長いメソッドの分割

問題: メソッドが長すぎて理解困難

typescript
// 問題のあるコード
class ReportGenerator {
    generateReport(data: any[]): string {
        // データの検証(20行)
        if (!data || data.length === 0) {
            throw new Error('データが必要です');
        }
        // データの変換(30行)
        const transformedData = data.map(item => {
            // 複雑な変換ロジック
        });
        // レポートの生成(40行)
        let report = '<html><body>';
        // HTMLの生成ロジック
        report += '</body></html>';
        return report;
    }
}

解決策: メソッドを小さな単位に分割

typescript
// 改善されたコード
class ReportGenerator {
    generateReport(data: any[]): string {
        this.validateData(data);
        const transformedData = this.transformData(data);
        return this.buildHtmlReport(transformedData);
    }
    
    private validateData(data: any[]): void {
        if (!data || data.length === 0) {
            throw new Error('データが必要です');
        }
    }
    
    private transformData(data: any[]): any[] {
        return data.map(item => this.transformItem(item));
    }
    
    private transformItem(item: any): any {
        // 変換ロジック
        return item;
    }
    
    private buildHtmlReport(data: any[]): string {
        const header = this.buildHeader();
        const body = this.buildBody(data);
        const footer = this.buildFooter();
        return `${header}${body}${footer}`;
    }
    
    private buildHeader(): string {
        return '<html><head><title>レポート</title></head><body>';
    }
    
    private buildBody(data: any[]): string {
        // ボディの生成ロジック
        return '';
    }
    
    private buildFooter(): string {
        return '</body></html>';
    }
}

2. 重複コードの排除

問題: 同じロジックが複数箇所に存在

typescript
// 重複のあるコード
class UserService {
    createUser(userData: any): User {
        // 検証ロジック
        if (!userData.email || !userData.email.includes('@')) {
            throw new Error('有効なメールアドレスが必要です');
        }
        if (!userData.password || userData.password.length < 8) {
            throw new Error('パスワードは8文字以上である必要があります');
        }
        
        return new User(userData);
    }
    
    updateUser(id: string, userData: any): User {
        // 同じ検証ロジック
        if (!userData.email || !userData.email.includes('@')) {
            throw new Error('有効なメールアドレスが必要です');
        }
        if (!userData.password || userData.password.length < 8) {
            throw new Error('パスワードは8文字以上である必要があります');
        }
        
        // 更新ロジック
        return this.updateUserData(id, userData);
    }
}

解決策: 共通ロジックを抽出

typescript
// 改善されたコード
class UserService {
    createUser(userData: any): User {
        this.validateUserData(userData);
        return new User(userData);
    }
    
    updateUser(id: string, userData: any): User {
        this.validateUserData(userData);
        return this.updateUserData(id, userData);
    }
    
    private validateUserData(userData: any): void {
        this.validateEmail(userData.email);
        this.validatePassword(userData.password);
    }
    
    private validateEmail(email: string): void {
        if (!email || !email.includes('@')) {
            throw new Error('有効なメールアドレスが必要です');
        }
    }
    
    private validatePassword(password: string): void {
        if (!password || password.length < 8) {
            throw new Error('パスワードは8文字以上である必要があります');
        }
    }
    
    private updateUserData(id: string, userData: any): User {
        // 更新ロジック
        return new User(userData);
    }
}

3. 大きなクラスの分割

問題: クラスが多すぎる責任を持っている

typescript
// 問題のあるコード
class UserManager {
    // ユーザー管理
    createUser(userData: any): User { /* ... */ }
    updateUser(id: string, userData: any): User { /* ... */ }
    deleteUser(id: string): void { /* ... */ }
    
    // 認証
    login(email: string, password: string): string { /* ... */ }
    logout(token: string): void { /* ... */ }
    
    // メール送信
    sendWelcomeEmail(user: User): void { /* ... */ }
    sendPasswordResetEmail(email: string): void { /* ... */ }
    
    // レポート生成
    generateUserReport(): string { /* ... */ }
    exportUserData(): string { /* ... */ }
}

解決策: 責任に基づいてクラスを分割

typescript
// 改善されたコード
class UserService {
    createUser(userData: any): User { /* ... */ }
    updateUser(id: string, userData: any): User { /* ... */ }
    deleteUser(id: string): void { /* ... */ }
}

class AuthenticationService {
    login(email: string, password: string): string { /* ... */ }
    logout(token: string): void { /* ... */ }
}

class EmailService {
    sendWelcomeEmail(user: User): void { /* ... */ }
    sendPasswordResetEmail(email: string): void { /* ... */ }
}

class UserReportService {
    generateUserReport(): string { /* ... */ }
    exportUserData(): string { /* ... */ }
}

// コーディネーターとして使用
class UserManager {
    constructor(
        private userService: UserService,
        private authService: AuthenticationService,
        private emailService: EmailService,
        private reportService: UserReportService
    ) {}
    
    async registerUser(userData: any): Promise<User> {
        const user = this.userService.createUser(userData);
        await this.emailService.sendWelcomeEmail(user);
        return user;
    }
}

コードの臭いと対処法

1. 長いパラメータリスト

問題:

typescript
function createUser(
    firstName: string,
    lastName: string,
    email: string,
    phone: string,
    address: string,
    city: string,
    country: string,
    zipCode: string
): User {
    // ...
}

解決策: パラメータオブジェクトの導入

typescript
interface UserData {
    firstName: string;
    lastName: string;
    email: string;
    phone: string;
    address: {
        street: string;
        city: string;
        country: string;
        zipCode: string;
    };
}

function createUser(userData: UserData): User {
    // ...
}

2. 特徴の妬み(Feature Envy)

問題: クラスが他のクラスのデータに過度に依存

typescript
class Order {
    constructor(private customer: Customer) {}
    
    calculateDiscount(): number {
        // 顧客データに過度に依存
        if (this.customer.isPremium() && 
            this.customer.getYearsActive() > 5 && 
            this.customer.getTotalPurchases() > 10000) {
            return 0.15;
        }
        return 0.05;
    }
}

解決策: ロジックを適切なクラスに移動

typescript
class Customer {
    isPremium(): boolean { /* ... */ }
    getYearsActive(): number { /* ... */ }
    getTotalPurchases(): number { /* ... */ }
    
    getDiscountRate(): number {
        if (this.isPremium() && 
            this.getYearsActive() > 5 && 
            this.getTotalPurchases() > 10000) {
            return 0.15;
        }
        return 0.05;
    }
}

class Order {
    constructor(private customer: Customer) {}
    
    calculateDiscount(): number {
        return this.customer.getDiscountRate();
    }
}

3. データクラス

問題: データのみを持つクラス

typescript
class Product {
    constructor(
        public name: string,
        public price: number,
        public category: string
    ) {}
}

// 他の場所でロジックが散在
function calculateTax(product: Product): number {
    if (product.category === 'food') {
        return product.price * 0.05;
    }
    return product.price * 0.1;
}

解決策: 関連するロジックをクラスに移動

typescript
class Product {
    constructor(
        private name: string,
        private price: number,
        private category: string
    ) {}
    
    getName(): string {
        return this.name;
    }
    
    getPrice(): number {
        return this.price;
    }
    
    getCategory(): string {
        return this.category;
    }
    
    calculateTax(): number {
        if (this.category === 'food') {
            return this.price * 0.05;
        }
        return this.price * 0.1;
    }
    
    getTotalPrice(): number {
        return this.price + this.calculateTax();
    }
}

自動リファクタリング

設定

json
{
    "refactoring": {
        "autoSave": true,
        "confirmBeforeRefactor": true,
        "updateImports": true,
        "formatAfterRefactor": true,
        "rules": {
            "maxMethodLength": 20,
            "maxParameterCount": 4,
            "maxClassLength": 200
        }
    }
}

カスタムリファクタリングルール

typescript
// .trae/refactoring-rules.ts
export const customRules = {
    // 長いメソッドの検出
    longMethod: {
        maxLines: 15,
        action: 'suggest-extract-method'
    },
    
    // 重複コードの検出
    duplicateCode: {
        minLines: 5,
        action: 'suggest-extract-function'
    },
    
    // 未使用のインポートの削除
    unusedImports: {
        action: 'auto-remove'
    }
};

ベストプラクティス

1. 段階的なリファクタリング

  • 小さな変更を積み重ねる
  • 各ステップでテストを実行
  • 一度に一つの問題に集中

2. テストファーストアプローチ

typescript
// リファクタリング前にテストを作成
describe('UserService', () => {
    test('should create user with valid data', () => {
        const userData = {
            email: 'test@example.com',
            password: 'password123'
        };
        
        const user = userService.createUser(userData);
        
        expect(user.email).toBe(userData.email);
    });
});

3. コードレビューの活用

  • リファクタリング前後でレビューを実施
  • チーム全体でコード品質基準を共有
  • 継続的な改善を心がける

4. ドキュメントの更新

typescript
/**
 * ユーザーサービス
 * 
 * ユーザーの作成、更新、削除を管理します。
 * 
 * @example
 * ```typescript
 * const userService = new UserService();
 * const user = userService.createUser({
 *   email: 'user@example.com',
 *   password: 'securePassword'
 * });
 * ```
 */
class UserService {
    /**
     * 新しいユーザーを作成します
     * 
     * @param userData - ユーザーデータ
     * @returns 作成されたユーザー
     * @throws {Error} 無効なデータの場合
     */
    createUser(userData: UserData): User {
        // 実装
    }
}

トラブルシューティング

よくある問題

  1. リファクタリング後のビルドエラー

    • インポートパスの確認
    • 型定義の更新
    • 依存関係の解決
  2. テストの失敗

    • テストケースの更新
    • モックの調整
    • アサーションの見直し
  3. パフォーマンスの低下

    • プロファイリングの実行
    • ボトルネックの特定
    • 最適化の適用

デバッグ支援

typescript
// リファクタリング前後の比較
console.log('リファクタリング前:', {
    methodCount: oldClass.methods.length,
    lineCount: oldClass.lineCount,
    complexity: oldClass.cyclomaticComplexity
});

console.log('リファクタリング後:', {
    methodCount: newClass.methods.length,
    lineCount: newClass.lineCount,
    complexity: newClass.cyclomaticComplexity
});

まとめ

リファクタリングは継続的なプロセスです。定期的にコードを見直し、改善の機会を見つけることで、長期的にメンテナンスしやすいコードベースを維持できます。

Traeの強力なリファクタリングツールを活用して、効率的にコード品質を向上させましょう。

究極の AI 駆動 IDE 学習ガイド