コンテンツにスキップ

プログラミング/リファクタリング

出典: フリー教科書『ウィキブックス(Wikibooks)』

リファクタリングとは

[編集]

リファクタリングは、既存のコードの構造を改善しながら、その外部の動作を変更しないソフトウェア開発技法です。主な目的は、コードの可読性、保守性、拡張性を向上させることです。

リファクタリングの基本原則

[編集]
  • 外部の振る舞いを変更しない
  • 段階的に小さな改善を行う
  • テストを常に通過させる
  • コードの重複を排除する
  • 明確な意図を持つコードを作成する

リファクタリングの典型的なパターン

[編集]

メソッド抽出 (Extract Method)

[編集]

Java での例

[編集]

リファクタリング前:

public class ReportGenerator {
    public void generateReport() {
        // データ収集
        List<Sale> sales = database.getAllSales();
        
        // データ処理
        double totalRevenue = 0;
        for (Sale sale : sales) {
            if (sale.getDate().getYear() == 2023) {
                totalRevenue += sale.getAmount();
                
                // レポート出力
                System.out.println("Sale: " + sale.getId());
                System.out.println("Amount: " + sale.getAmount());
            }
        }
        
        // 最終集計
        System.out.println("Total Revenue: " + totalRevenue);
    }
}

リファクタリング後:

public class ReportGenerator {
    public void generateReport() {
        List<Sale> sales = database.getAllSales();
        List<Sale> filteredSales = filterSalesForCurrentYear(sales);
        
        double totalRevenue = calculateTotalRevenue(filteredSales);
        printSalesReport(filteredSales, totalRevenue);
    }
    
    private List<Sale> filterSalesForCurrentYear(List<Sale> sales) {
        return sales.stream()
            .filter(sale -> sale.getDate().getYear() == 2023)
            .collect(Collectors.toList());
    }
    
    private double calculateTotalRevenue(List<Sale> sales) {
        return sales.stream()
            .mapToDouble(Sale::getAmount)
            .sum();
    }
    
    private void printSalesReport(List<Sale> sales, double totalRevenue) {
        sales.forEach(sale -> {
            System.out.println("Sale: " + sale.getId());
            System.out.println("Amount: " + sale.getAmount());
        });
        
        System.out.println("Total Revenue: " + totalRevenue);
    }
}

クラスの抽出 (Extract Class)

[編集]

Kotlin での例

[編集]
// リファクタリング前
class Employee {
    var name: String = ""
    var phoneNumber: String = ""
    var address: String = ""
    var city: String = ""
    var state: String = ""
    var zipCode: String = ""
    
    fun printContactInfo() {
        println("$name - $phoneNumber")
        println("$address, $city, $state $zipCode")
    }
}

// リファクタリング後
data class Address(
    val street: String,
    val city: String,
    val state: String,
    val zipCode: String
) {
    fun formatAddress(): String = "$street, $city, $state $zipCode"
}

data class ContactInfo(
    val phoneNumber: String,
    val email: String
)

class Employee(
    val name: String,
    val address: Address,
    val contactInfo: ContactInfo
) {
    fun printContactInfo() {
        println("$name - ${contactInfo.phoneNumber}")
        println(address.formatAddress())
    }
}

パラメータ・オブジェクトの導入

[編集]

Scala での例

[編集]
// リファクタリング前
class UserService {
  def createUser(
    firstName: String, 
    lastName: String, 
    email: String, 
    age: Int, 
    country: String
  ): User = {
    // ユーザー作成ロジック
    val user = new User()
    user.firstName = firstName
    user.lastName = lastName
    user.email = email
    user.age = age
    user.country = country
    user
  }

  def updateUser(
    userId: Int,
    firstName: String, 
    lastName: String, 
    email: String, 
    age: Int, 
    country: String
  ): Unit = {
    // ユーザー更新ロジック
  }
}

// リファクタリング後
case class UserData(
  firstName: String,
  lastName: String,
  email: String,
  age: Int,
  country: String
)

class UserService {
  def createUser(userData: UserData): User = {
    val user = new User()
    user.firstName = userData.firstName
    user.lastName = userData.lastName
    user.email = userData.email
    user.age = userData.age
    user.country = userData.country
    user
  }

  def updateUser(userId: Int, userData: UserData): Unit = {
    // より簡潔で柔軟な更新処理
  }
}

テンプレートメソッドパターン

[編集]

TypeScript での例

[編集]
// リファクタリング前
class DataProcessor {
  processCSV(data: string) {
    // CSV特有の前処理
    const lines = data.split('\n');
    const cleanedLines = lines.filter(line => line.trim() !== '');
    
    // パース処理
    const parsedData = cleanedLines.map(line => line.split(','));
    
    // データ変換
    const processedData = parsedData.map(row => {
      return {
        id: row[0],
        name: row[1],
        value: parseFloat(row[2])
      };
    });
    
    return processedData;
  }

  processJSON(data: string) {
    // JSON特有の前処理
    const cleanedData = data.trim();
    
    // パース処理
    const parsedData = JSON.parse(cleanedData);
    
    // データ変換
    const processedData = parsedData.map((item: any) => ({
      id: item.id,
      name: item.name,
      value: parseFloat(item.value)
    }));
    
    return processedData;
  }
}

// リファクタリング後
abstract class DataProcessor {
  // テンプレートメソッド
  process(data: string) {
    const cleanedData = this.preprocess(data);
    const parsedData = this.parse(cleanedData);
    return this.transform(parsedData);
  }

  protected abstract preprocess(data: string): string;
  protected abstract parse(data: string): any[];
  
  protected transform(data: any[]): any[] {
    return data.map(item => ({
      id: item.id,
      name: item.name,
      value: parseFloat(item.value)
    }));
  }
}

class CSVProcessor extends DataProcessor {
  protected preprocess(data: string): string {
    return data.split('\n').filter(line => line.trim() !== '').join('\n');
  }

  protected parse(data: string): any[] {
    return data.split('\n').map(line => {
      const [id, name, value] = line.split(',');
      return { id, name, value };
    });
  }
}

class JSONProcessor extends DataProcessor {
  protected preprocess(data: string): string {
    return data.trim();
  }

  protected parse(data: string): any[] {
    return JSON.parse(data);
  }
}

ガード節 (Guard Clause)

[編集]

Rust での例

[編集]
// リファクタリング前
fn process_user_data(user: &User) -> Result<ProcessedData, Error> {
    if user.is_active {
        if user.age >= 18 {
            if !user.email.is_empty() {
                // 本格的な処理
                let processed_data = ProcessedData {
                    id: user.id,
                    name: user.name.clone(),
                    // 他のフィールド
                };
                Ok(processed_data)
            } else {
                Err(Error::InvalidEmail)
            }
        } else {
            Err(Error::UnderAge)
        }
    } else {
        Err(Error::InactiveUser)
    }
}

// リファクタリング後
fn process_user_data(user: &User) -> Result<ProcessedData, Error> {
    // ガード節による早期リターン
    if !user.is_active {
        return Err(Error::InactiveUser);
    }

    if user.age < 18 {
        return Err(Error::UnderAge);
    }

    if user.email.is_empty() {
        return Err(Error::InvalidEmail);
    }

    // クリーンで明確な処理
    let processed_data = ProcessedData {
        id: user.id,
        name: user.name.clone(),
        // 他のフィールド
    };

    Ok(processed_data)
}

リファクタリングの注意点

[編集]
  1. 過度なリファクタリングは避ける
  2. 既存のテストスイートを活用する
  3. 小さな増分で行う
  4. コードレビューを活用する

リファクタリングのベストプラクティス

[編集]
  • 重複コードを排除する
  • メソッドとクラスの責務を明確にする
  • 名前を意味のあるものに変更する
  • 複雑な条件式をシンプルにする
  • デザインパターンを適切に適用する

まとめ

[編集]

リファクタリングは、コードの品質を継続的に改善するための重要な技術です。システムの機能を保ちながら、コードの構造と可読性を向上させることができます。