目录
- 提取方法(Extract Method)
- 内联方法(Inline Method)
- 提取变量(Extract Variable)
- 内联临时变量(Inline Temp)
- 以查询取代临时变量(Replace Temp with Query)
- 拆分临时变量(Split Temporary Variable)
- 移除参数赋值(Remove Assignments to Parameters)
- 用方法对象替代方法(Replace Method with Method Object)
- 替换算法(Substitute Algorithm)
- 引入解释性变量(Introduce Explaining Variable)
一、提炼方法(Extract Method)
1.概述
提炼方法(Extract Method) 指的是将某个方法或函数中某一段独立的、具有特定功能的代码块提取出来,封装成一个独立的方法。通过这一操作,可以使代码变得更加简洁、易于理解和维护。
适用场景
- 方法过长:当一个方法的代码行数过多,导致阅读困难时,可以考虑将其拆分为多个小方法。
- 重复代码:当多个方法中存在相似或相同的代码时,可以将其提取为一个独立的方法,以减少重复。
- 逻辑复杂:当方法内部包含复杂的逻辑判断或计算时,可以将其提炼成独立的方法,以提高代码的清晰度。
- 提高可复用性:当某段代码在多个地方可能被使用时,将其提炼为独立的方法,有助于代码的复用。
理想状态:每个方法的命名就是对它内部细节的最高层次概括。
2.重构前
public void ProcessOrder(Order order)
{
Console.WriteLine("Processing order...");
double total = 0;
foreach (var item in order.Items)
{
total += item.Price * item.Quantity;
}
if (order.Customer.IsPremium)
{
total *= 0.9;
}
total *= 1.2;
Console.WriteLine($"Order ID: {order.Id}");
Console.WriteLine($"Customer: {order.Customer.Name}");
Console.WriteLine($"Total due: {total:C}");
Console.WriteLine("Order processed.");
}
- 方法过长:
ProcessOrder方法包含了多个职责,如计算订单总额、应用折扣、计算税费和打印订单信息,使得方法过于庞大。 - 职责不单一:该方法同时处理了多个业务逻辑,违背了单一职责原则。
- 可读性差:将所有逻辑放在一个方法中,导致代码难以理解和维护。
3.重构后
public void ProcessOrder(Order order)
{
Console.WriteLine("Processing order...");
double total = CalculateOrderTotal(order);
total = ApplyDiscount(total, order.Customer);
total = AddTax(total);
PrintOrderSummary(order, total);
Console.WriteLine("Order processed.");
}
private double CalculateOrderTotal(Order order)
{
double total = 0;
foreach (var item in order.Items)
{
total += item.Price * item.Quantity;
}
return total;
}
private double ApplyDiscount(double total, Customer customer)
{
if (customer.IsPremium)
{
return total * 0.9;
}
return total;
}
private double AddTax(double total)
{
return total * 1.2;
}
private void PrintOrderSummary(Order order, double total)
{
Console.WriteLine($"Order ID: {order.Id}");
Console.WriteLine($"Customer: {order.Customer.Name}");
Console.WriteLine($"Total due: {total:C}");
}
- 提高可读性:每个方法都有明确的职责,代码更易于理解。
- 增强可维护性:修改某一业务逻辑时,只需在对应的方法中进行修改,减少了对其他部分的影响。
- 促进代码复用:将通用的逻辑提取为独立的方法,便于在其他地方复用。
- 符合单一职责原则:每个方法只处理一个特定的任务,符合设计原则。
二、内联方法(Inline Method)
1.概述
内联方法(Inline Method) 旨在将方法调用替换为其方法体的内容,从而消除不必要的间接调用。这种做法通常适用于方法体非常简单、调用频繁且没有额外逻辑的情况。通过这种方式,可以减少代码的复杂性,提高可读性和维护性。
适用场景:
- 方法体简单:方法体仅包含一行或少量代码,且该代码足够直观。
- 方法调用频繁:该方法在代码中被多次调用,且每次调用的上下文相似。
- 无额外逻辑:方法没有副作用,如修改全局状态或执行复杂计算。
- 提高可读性:内联后,代码的意图更加明确,易于理解。
2.重构前
public class Order
{
private int _numberOfLateReturns;
public int GetRating()
{
return MoreThanThreeLateReturns() ? 0 : 1;
}
private bool MoreThanThreeLateReturns()
{
return _numberOfLateReturns > 3;
}
}
- 方法过于简单:
MoreThanThreeLateReturns方法仅返回一个条件判断,实际代码中并未增加额外的逻辑或可读性。 - 增加了代码复杂性:引入了额外的方法调用,增加了理解和维护的难度。
- 违反了“不要过度抽象”原则:在这种情况下,过度抽象反而使代码变得冗长。
3.重构后
public class Order
{
private int _numberOfLateReturns;
public int GetRating()
{
return _numberOfLateReturns > 3 ? 0 : 1;
}
}
- 提高可读性:直接表达了“如果迟还超过三次,则评级为 0,否则为 1”的逻辑。
- 减少了不必要的间接调用:代码更简洁,减少了方法调用的层级。
- 符合“简单优于复杂”原则:代码更直观,易于理解。
三、提取变量(Extract Variable)
1.概述
提取变量(Extract Variable) 是一种重构技术,旨在将复杂的表达式或重复使用的代码片段提取为具名的局部变量。 这一过程提高了代码的可读性、可维护性和可测试性。
适用场景
- 复杂表达式:当条件判断或计算表达式过于复杂时,提取中间结果为变量可以增强可读性。
- 重复代码:当相同的表达式在多个地方出现时,提取为变量可以减少重复代码。
- 长方法:当方法体过长时,提取变量可以帮助分解复杂逻辑。
- 嵌套逻辑:当存在多层嵌套的条件判断时,提取变量可以简化逻辑结构。
2.重构前
public void ProcessOrder(Order order)
{
if (order.Quantity * order.Price > 1000 && order.Customer.IsPremium)
{
// 执行某些操作
}
}
order.Quantity * order.Price的计算逻辑不明确,可能导致理解困难。- 条件判断过于复杂,增加了出错的概率。
- 重复计算了
order.Quantity * order.Price,降低了效率。
3.重构后
public void ProcessOrder(Order order)
{
var totalAmount = order.Quantity * order.Price;
var isPremiumCustomer = order.Customer.IsPremium;
if (totalAmount > 1000 && isPremiumCustomer)
{
// 执行某些操作
}
}
- 通过引入
totalAmount和isPremiumCustomer,使代码的意图更加明确。 - 避免了重复计算,提高了代码效率。
- 简化了条件判断,降低了出错的概率。
四、内联临时变量(Inline Temp)
1.概述
内联临时变量(Inline Temp) 是一种代码重构技术,旨在消除那些只使用一次且赋值后不再修改的临时变量。通过将这些变量直接替换为其赋值表达式,可以提高代码的简洁性和可读性。
适用场景
- 临时变量只被使用一次:如果一个变量在赋值后仅被使用一次,保留该变量可能会增加代码的复杂性。
- 变量命名不具备表达性:当变量名无法清晰表达其用途时,内联该变量可以提高代码的可理解性。
- 赋值表达式简单:赋值给临时变量的表达式较为简单,直接使用该表达式不会影响代码的可读性。
2.重构前
public decimal CalculateDiscount(decimal price)
{
decimal discountRate = GetDiscountRate();
return price - (discountRate * price);
}
discountRate变量仅在return语句中使用一次,且其赋值表达式GetDiscountRate()直接返回一个值。
3.重构后
public decimal CalculateDiscount(decimal price)
{
return price - (GetDiscountRate() * price);
}
- 通过将
GetDiscountRate()的调用直接嵌入到return语句中,消除了临时变量discountRate,使代码更加简洁。
五、以查询取代临时变量(Replace Temp with Query)
1.概述
“以查询取代临时变量”(Replace Temp with Query) 是指当你在方法中使用临时变量存储某个表达式的计算结果时,可以考虑将该表达式提炼为一个独立的查询方法。这样做不仅可以提高代码的可读性,还能为后续的重构和维护提供便利。
适用场景
- 临时变量用于存储复杂表达式的结果:如果临时变量存储了一个复杂的表达式结果,并且该表达式在方法中多次使用,可以考虑将其提取为一个查询方法。
- 临时变量的计算逻辑具有独立性:如果临时变量的计算逻辑可以独立于当前方法存在,并且具有一定的业务意义,提取为查询方法可以提高代码的模块化程度。
- 提高代码的可读性和可维护性:通过将复杂的表达式提取为具有描述性名称的查询方法,可以使代码的意图更加明确,减少理解上的障碍。
2.重构前
public class Order
{
private int _numberOfLateReturns;
public int GetRating()
{
double basePrice = _numberOfLateReturns * 100; // 假设每次迟还扣除100元
if (basePrice > 1000)
{
return 0; // 评级为0
}
else
{
return 1; // 评级为1
}
}
}
- 临时变量的计算逻辑与业务逻辑混杂:
basePrice的计算逻辑直接嵌入在GetRating方法中,导致方法的职责不单一。 - 难以维护和扩展:如果未来需要调整
basePrice的计算逻辑,可能需要修改多个方法,增加了维护的难度。 - 降低代码的可读性:
basePrice的计算逻辑可能对其他开发人员不直观,增加了理解的成本。
3.重构后
public class Order
{
private int _numberOfLateReturns;
public int GetRating()
{
if (BasePrice() > 1000)
{
return 0; // 评级为0
}
else
{
return 1; // 评级为1
}
}
private double BasePrice()
{
return _numberOfLateReturns * 100; // 假设每次迟还扣除100元
}
}
- 提高代码的可读性:
BasePrice方法的名称清晰地表达了其计算的内容,使代码的意图更加明确。 - 增强代码的可维护性:如果未来需要调整
basePrice的计算逻辑,只需修改BasePrice方法,避免了在多个方法中重复修改。 - 符合单一职责原则:
GetRating方法专注于获取评级,BasePrice方法专注于计算基本价格,职责更加清晰。
六、拆分临时变量
1.概述
“拆分临时变量”(Split Temporary Variable) 是指将一个临时变量的多次赋值拆分成多个独立的临时变量,以提高代码的可读性和可维护性。
在某些情况下,临时变量可能会被赋值多次,每次赋值代表不同的逻辑目的。这种做法会导致临时变量承担多个职责,增加代码的复杂性。通过将其拆分为多个独立的临时变量,可以使代码更加清晰,职责更加明确。
适用场景
- 临时变量被赋值多次:如果一个临时变量在同一方法中被赋值多次,每次赋值代表不同的逻辑目的。
- 提高代码的可读性和可维护性:通过将一个临时变量拆分为多个独立的临时变量,可以使代码的意图更加明确,减少理解上的障碍。
- 遵循单一职责原则:每个临时变量只承担一个职责,符合单一职责原则
2.重构前
public class Rectangle
{
private int _width;
private int _height;
public void PrintAreaAndPerimeter()
{
int temp = 2 * (_width + _height);
Console.WriteLine($"Perimeter: {temp}");
temp = _width * _height;
Console.WriteLine($"Area: {temp}");
}
}
- 临时变量的多次赋值:
temp变量被赋值两次,分别用于存储周长和面积,导致其承担了多个职责。 - 降低代码的可读性:
temp的值在两次赋值之间发生变化,增加了理解的难度。 - 违反了单一职责原则:
temp变量同时承担了计算周长和计算面积的职责。
3.重构后
public class Rectangle
{
private int _width;
private int _height;
public void PrintAreaAndPerimeter()
{
int perimeter = 2 * (_width + _height);
Console.WriteLine($"Perimeter: {perimeter}");
int area = _width * _height;
Console.WriteLine($"Area: {area}");
}
}
- 提高代码的可读性:
perimeter和area变量的名称清晰地表达了其存储的内容,使代码的意图更加明确。 - 增强代码的可维护性:如果未来需要调整周长或面积的计算逻辑,只需修改对应的变量,避免了在多个地方修改。
- 符合单一职责原则:每个临时变量只承担一个职责,职责更加清晰。
七、移除参数赋值(Remove Assignments to Parameters)
1.概述
移除参数赋值(Remove Assignments to Parameters) 是指消除在方法内部对参数的赋值操作。这种做法有助于提高代码的可读性、可维护性和可预测性。
通常做法
- 建立一个临时变量,将待处理的参数值赋予它。
- 以“对参数的赋值”为界,将其后所有对此参数的引用点,全部替换为“对此临时变量的引用”。
- 修改赋值语句,使其改为对新建的临时变量赋值。
2.重构前
public class Order
{
private int _numberOfLateReturns;
public int GetRating(int inputVal)
{
if (inputVal > 50)
{
inputVal -= 2;
}
return inputVal;
}
}
- 参数被修改:
inputVal参数在方法内部被修改,可能导致调用者误以为传入的参数值会被改变。 - 降低代码的可预测性:修改参数的值可能导致方法的行为不明确,增加了代码的复杂性。
- 违反单一职责原则:方法同时承担了修改参数和执行业务逻辑的职责,导致职责不清晰。
3.重构后
public class Order
{
private int _numberOfLateReturns;
public int GetRating(int inputVal)
{
int result = inputVal;
if (result > 50)
{
result -= 2;
}
return result;
}
}
- 提高代码的可读性:
result变量的名称清晰地表达了其存储的内容,使代码的意图更加明确。 - 增强代码的可维护性:如果未来需要调整折扣逻辑,只需修改
GetRating方法,避免了在多个地方修改。 - 符合单一职责原则:
GetRating方法专注于获取评级,职责更加清晰。
八、用方法对象替代方法(Replace Method with Method Object)
1.概述
“用方法对象替代方法”(Replace Method with Method Object) 是指将一个复杂的、包含多个局部变量和逻辑的长方法提取到一个新的类中,使其成为该类的一个方法对象。这种做法有助于解决方法过长、局部变量交织复杂、难以提取子方法等问题。通过将方法提取到独立的类中,可以将局部变量转化为该类的字段,从而更容易地拆分复杂的逻辑。
适用场景
- 方法过长:方法包含大量的逻辑和局部变量,导致代码难以理解和维护。
- 局部变量交织复杂:方法中的局部变量之间相互依赖,难以独立提取子方法。
- 需要拆分复杂逻辑:希望将复杂的逻辑拆分成多个小的、易于理解和维护的部分。
- 方法内部逻辑独立:方法内部的逻辑可以独立于原始类存在,适合提取到新的类中。
2.重构前
public class Order
{
private int _numberOfLateReturns;
private double _basePrice;
private double _discount;
public double CalculateTotal()
{
double primaryBasePrice = _basePrice;
double secondaryBasePrice = primaryBasePrice * 0.1;
double tertiaryBasePrice = secondaryBasePrice * 0.05;
double discount = _discount;
double total = primaryBasePrice + secondaryBasePrice + tertiaryBasePrice - discount;
return total;
}
}
- 方法过长:
CalculateTotal方法包含了多个计算逻辑,导致方法过长。 - 局部变量交织复杂:
primaryBasePrice、secondaryBasePrice、tertiaryBasePrice和discount等局部变量之间相互依赖,难以独立提取子方法。 - 难以扩展:如果未来需要调整计算逻辑,可能需要修改多个地方,增加了维护的难度。
3.重构后
public class Order
{
private int _numberOfLateReturns;
private double _basePrice;
private double _discount;
public double CalculateTotal()
{
return new PriceCalculator(this).Compute();
}
}
public class PriceCalculator
{
private double _primaryBasePrice;
private double _secondaryBasePrice;
private double _tertiaryBasePrice;
private double _discount;
public PriceCalculator(Order order)
{
_primaryBasePrice = order._basePrice;
_secondaryBasePrice = _primaryBasePrice * 0.1;
_tertiaryBasePrice = _secondaryBasePrice * 0.05;
_discount = order._discount;
}
public double Compute()
{
return _primaryBasePrice + _secondaryBasePrice + _tertiaryBasePrice - _discount;
}
}
- 提高代码的可读性:
PriceCalculator类的名称清晰地表达了其功能,使代码的意图更加明确。 - 增强代码的可维护性:如果未来需要调整价格计算逻辑,只需修改
PriceCalculator类,避免了在多个地方修改。 - 符合单一职责原则:
Order类专注于订单的管理,PriceCalculator类专注于价格的计算,职责更加清晰。
九、替换算法(Substitute Algorithm)
1.概述
替换算法(Substitute Algorithm) 是指将现有方法中的复杂或低效算法替换为更简单、清晰或高效的算法。这种做法有助于提高代码的可读性、可维护性和性能。
适用场景
- 现有算法过于复杂:实现逻辑冗长、嵌套深,影响代码可读性。
- 性能问题:当前算法效率低,影响系统运行性能。
- 更好的替代方案:有更直观或更高效的算法能够完成相同功能。
2.重构前
假设我们有一个计算两个整数最大公约数(GCD)的方法,使用了暴力法:
public int CalculateGCD(int a, int b)
{
List<int> divisorsA = GetDivisors(a);
List<int> divisorsB = GetDivisors(b);
int gcd = 1;
foreach (var divisor in divisorsA)
{
if (divisorsB.Contains(divisor))
{
gcd = Math.Max(gcd, divisor);
}
}
return gcd;
}
private List<int> GetDivisors(int number)
{
List<int> divisors = new List<int>();
for (int i = 1; i <= number; i++)
{
if (number % i == 0)
{
divisors.Add(i);
}
}
return divisors;
}
- 性能低下:获取所有约数的方法效率低,尤其是输入较大时。
- 算法复杂:算法使用了多个循环和集合操作,显得冗长复杂。
3.重构后
使用更高效的欧几里得算法代替:
public int CalculateGCD(int a, int b)
{
while (b != 0)
{
int temp = b;
b = a % b;
a = temp;
}
return a;
}
- 性能提升:复杂度从 O(n) 降至 O(log(min(a, b)))。
- 代码简洁:算法逻辑清晰,易于理解和维护。
- 符合数学原理:欧几里得算法是计算 GCD 的经典方法,广泛应用于数学和计算机科学中。
十、引入解释性变量(Introduce Explaining Variable)
1.概述
引入解释性变量(Introduce Explaining Variable) 是指通过将复杂表达式的部分结果赋值给具有描述性的临时变量,从而提高代码的可读性和可维护性。这种做法有助于使代码的意图更加明确,减少理解上的障碍。
适用场景
- 复杂的布尔表达式:条件判断中包含多个逻辑操作,导致表达式难以理解。
- 嵌套的数学计算:数学表达式中包含多个嵌套的运算,影响代码的可读性。
- 重复使用的表达式:相同的表达式在代码中多次出现,容易引发错误。
- 需要提高代码的可读性和可维护性:通过引入解释性变量,使代码的意图更加明确,便于后续的维护和扩展。
2.重构前
public bool CanAccess(User user)
{
return (user.Role == "Admin" && user.IsActive) || (user.Role == "User" && user.IsActive && user.HasPaid);
}
- 表达式复杂:条件判断中包含多个逻辑操作,导致表达式难以理解。
- 可读性差:代码的意图不明确,需要花费时间去理解每个条件的含义。
3.重构后
public bool CanAccess(User user)
{
bool isAdminAndActive = user.Role == "Admin" && user.IsActive;
bool isUserAndEligible = user.Role == "User" && user.IsActive && user.HasPaid;
return isAdminAndActive || isUserAndEligible;
}
- 提高代码的可读性:解释性变量的名称清晰地表达了其含义,使代码的意图更加明确。
- 增强代码的可维护性:如果未来需要调整访问权限的逻辑,只需修改对应的解释性变量,避免了在多个地方修改。
- 便于调试和测试:可以单独测试每个解释性变量的逻辑,便于发现和修复问题。