プログラミング/リファクタリング
< プログラミング
リファクタリングとは
編集リファクタリングは、既存のコードの構造を改善しながら、その外部の動作を変更しないソフトウェア開発技法です。主な目的は、コードの可読性、保守性、拡張性を向上させることです。
リファクタリングの基本原則
編集- 外部の振る舞いを変更しない
- 段階的に小さな改善を行う
- テストを常に通過させる
- コードの重複を排除する
- 明確な意図を持つコードを作成する
リファクタリングの典型的なパターン
編集メソッド抽出 (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) }
リファクタリングの注意点
編集- 過度なリファクタリングは避ける
- 既存のテストスイートを活用する
- 小さな増分で行う
- コードレビューを活用する
リファクタリングのベストプラクティス
編集- 重複コードを排除する
- メソッドとクラスの責務を明確にする
- 名前を意味のあるものに変更する
- 複雑な条件式をシンプルにする
- デザインパターンを適切に適用する
まとめ
編集リファクタリングは、コードの品質を継続的に改善するための重要な技術です。システムの機能を保ちながら、コードの構造と可読性を向上させることができます。