承接《重構》書籍方法層面重構手法,本文涵蓋第 5-9 條模式:引入解釋變數、分割暫時變數、移除參數賦值、以方法物件取代方法、替換演算法,附前後代碼對照與完整重構演示。
複雜表達式拆解為具語意的暫時變數,提升可讀性。
// Before
double price() {
return quantity * itemPrice
- Math.max(0, quantity-500) * itemPrice * 0.05
+ Math.min(quantity * itemPrice * 0.1, 100.0);
}
// After
double price() {
final double basePrice = quantity * itemPrice;
final double quantityDiscount = Math.max(0, quantity-500) * itemPrice * 0.05;
final double shipping = Math.min(quantity * itemPrice * 0.1, 100.0);
return basePrice - quantityDiscount + shipping;
}
// 進階:萃取方法
double price() { return basePrice() - quantityDiscount() + shipping(); }
private double basePrice() { return quantity * itemPrice; }
private double quantityDiscount() { return Math.max(0, quantity-500) * itemPrice * 0.05; }
private double shipping() { return Math.min(quantity * itemPrice * 0.1, 100.0); }
同一暫時變數承擔多重職責時,拆分為多個 final 變數。
// Before
double getDistanceTravelled(int time) {
double result, acc = primaryForce/mass;
int primaryTime = Math.min(time, delay);
result = 0.5 * acc * primaryTime * primaryTime;
int secondaryTime = time - delay;
if (secondaryTime > 0) {
double primaryVel = acc * delay;
acc = (primaryForce+secondaryForce)/mass; // 復用 acc 變數
result += primaryVel*secondaryTime + 0.5*acc*secondaryTime*secondaryTime;
}
return result;
}
// After
double getDistanceTravelled(int time) {
double result;
final double primaryAcc = primaryForce/mass;
int primaryTime = Math.min(time, delay);
result = 0.5 * primaryAcc * primaryTime * primaryTime;
int secondaryTime = time - delay;
if (secondaryTime > 0) {
double primaryVel = primaryAcc * delay;
final double secondaryAcc = (primaryForce+secondaryForce)/mass;
result += primaryVel*secondaryTime + 0.5*secondaryAcc*secondaryTime*secondaryTime;
}
return result;
}
// 完整萃取
private double getPrimaryAcc() { return primaryForce/mass; }
private int getPrimaryTime(int time) { return Math.min(time, delay); }
private int getSecondaryTime(int time) { return time - delay; }
參數不應被重新賦值,改用區域變數。
// Before
int discount(int inputVal, int quantity, int yearToDate) {
if (inputVal > 50) inputVal -= 2;
if (quantity > 100) inputVal -= 1;
if (yearToDate > 10000) inputVal -= 4;
return inputVal;
}
// After
int discount(int inputVal, int quantity, int yearToDate) {
int result = inputVal;
if (inputVal > 50) result -= 2;
if (quantity > 100) result -= 1;
if (yearToDate > 10000) result -= 4;
return result;
}
過長方法、大量區域變數難以萃取時,轉為獨立類別,區域變數變欄位,邏輯分解為多個私有方法。
// Before
class Account {
int gamma(int inputVal, int quantity, int yearToDate) {
int importantVal1 = (inputVal*quantity) + delta();
int importantVal2 = (inputVal*yearToDate) + 100;
if (yearToDate - importantVal1 > 100) importantVal2 -= 20;
int importantVal3 = importantVal2 * 7;
return importantVal3 - 2*importantVal1;
}
}
// After
class Account {
int gamma(int inputVal, int quantity, int yearToDate) {
return new Gamma(this, inputVal, quantity, yearToDate).compute();
}
}
class Gamma {
private final Account account;
private final int inputVal, quantity, yearToDate;
private int importantVal1, importantVal2, importantVal3;
Gamma(Account account, int inputVal, int quantity, int yearToDate) {
this.account = account; this.inputVal = inputVal;
this.quantity = quantity; this.yearToDate = yearToDate;
}
int compute() {
importantVal1 = (inputVal*quantity) + account.delta();
importantVal2 = (inputVal*yearToDate) + 100;
if (yearToDate - importantVal1 > 100) importantVal2 -= 20;
importantVal3 = importantVal2 * 7;
return importantVal3 - 2*importantVal1;
}
}
以更清晰、高效的演算法替換現有實作,前提是測試保護下安全重構。
// 範例:排序、查找、計算邏輯替換
// 重點:有完整測試覆蓋時,大膽替換演算法本體
這些方法層面重構手法,是清理「代碼壞味道」的手術刀,持續練習才能內化為肌肉記憶。