重构基础手法(上):提取方法、内联方法、引入解释变量
各位同学,咱们今天聊点实在的。
重构这件事,说白了就是「改代码但不改功能」。听起来简单,做起来门道不少。我刚开始带团队那会儿,最头疼的就是看到一段几百行的方法,逻辑全揉在一起,改一个 bug 能引出三个新 bug。后来我学乖了——先把代码拆清楚,再动手改。
今天咱们先讲三个最基础、也最常用的手法:提取方法、内联方法、引入解释变量。这三招用好了,你的代码可读性至少提升一个档次。
1. 提取方法(Extract Method)
这是我最常用的重构手法,没有之一。
什么叫提取方法?就是把一段逻辑独立的代码,从一个大方法里「拎」出来,变成一个单独的方法。你想想看,一个方法如果超过 20 行,读起来就开始费劲了。超过 50 行?那基本就是「屎山」的雏形。
核心原则:一个方法只做一件事。如果一件事里还套着另一件事,那就拆。
举个例子,假设你有一段代码,用来计算订单的折扣:
// 重构前
public decimal CalculateTotal(Order order)
{
decimal total = 0;
foreach (var item in order.Items)
{
total += item.Price * item.Quantity;
}
// 会员折扣
if (order.Customer.IsVIP)
{
total *= 0.9m;
}
// 满减活动
if (total > 1000)
{
total -= 100;
}
return total;
}
这段代码看着还行?其实已经混了三件事:计算小计、会员折扣、满减活动。我建议你拆成三个方法:
// 重构后
public decimal CalculateTotal(Order order)
{
decimal subtotal = CalculateSubtotal(order);
decimal afterVipDiscount = ApplyVipDiscount(subtotal, order.Customer);
decimal finalTotal = ApplyPromotion(afterVipDiscount);
return finalTotal;
}
private decimal CalculateSubtotal(Order order)
{
decimal total = 0;
foreach (var item in order.Items)
{
total += item.Price * item.Quantity;
}
return total;
}
private decimal ApplyVipDiscount(decimal total, Customer customer)
{
return customer.IsVIP ? total * 0.9m : total;
}
private decimal ApplyPromotion(decimal total)
{
return total > 1000 ? total - 100 : total;
}
你看,现在每个方法的名字就是它的注释。读代码的人一眼就知道「哦,这里在算小计,那里在打折」。我在项目中遇到过很多次,这种拆法对后期维护帮助极大——改折扣规则?直接改 ApplyVipDiscount 就行,不会影响到其他逻辑。
小技巧:提取方法时,方法名要能准确描述「做了什么」,而不是「怎么做的」。比如 CalculateSubtotal 就比 LoopItemsAndSum 好得多。
2. 内联方法(Inline Method)
有提取就有内联。这两个手法其实是反过来的。
内联方法的意思是:如果一个方法本身太简单,或者只被调用了一次,那不如直接把它的代码「塞回」调用处。为什么要这么做?因为方法调用本身也有成本——不是性能成本,而是阅读成本。你读代码时跳来跳去,脑子容易断片。
举个例子:
// 重构前
public void ProcessOrder(Order order)
{
ValidateOrder(order);
// ... 其他逻辑
}
private void ValidateOrder(Order order)
{
if (order == null) throw new ArgumentNullException(nameof(order));
}
这个 ValidateOrder 方法就一行,而且只在一个地方调用。我个人习惯会把它内联掉:
// 重构后
public void ProcessOrder(Order order)
{
if (order == null) throw new ArgumentNullException(nameof(order));
// ... 其他逻辑
}
嗯,这里要注意:内联方法不是「无脑合并」。如果一个方法虽然简单,但被多处调用,那就别内联——保持复用性更重要。我曾经犯过这个错,把一段校验逻辑内联到三个地方,后来改需求时改了三次,累得够呛。
避坑指南:内联方法前,先确认这个方法是否被多处调用。如果只有一处调用,且方法体不超过 3-5 行,内联是安全的。否则,保持原样。
3. 引入解释变量(Introduce Explaining Variable)
这个手法,说白了就是「给复杂的表达式起个名字」。
你想想看,一段代码里如果出现 a * b + c / d - e % f 这种表达式,读起来是不是很头疼?更别说条件判断里那种又长又臭的逻辑了。
举个例子:
// 重构前
if (order.Customer.Age > 60 && order.Customer.MembershipYears > 5 && order.TotalAmount > 500)
{
// 给老年忠实客户打八折
order.TotalAmount *= 0.8m;
}
这段条件判断,读一遍得花好几秒。我建议你引入解释变量:
// 重构后
bool isSeniorLoyalCustomer = order.Customer.Age > 60 && order.Customer.MembershipYears > 5;
bool isHighValueOrder = order.TotalAmount > 500;
if (isSeniorLoyalCustomer && isHighValueOrder)
{
order.TotalAmount *= 0.8m;
}
你看,现在条件判断变成了「如果他是老年忠实客户,并且订单金额高」,读起来就像自然语言一样顺畅。我在项目中遇到过很多类似的场景,尤其是业务规则复杂的系统,引入解释变量几乎是必用的手法。
小技巧:解释变量的名字要体现「业务含义」,而不是「计算过程」。比如 isSeniorLoyalCustomer 就比 ageGt60AndMembershipGt5 好得多。
三者如何配合使用?
这三个手法不是孤立的。在实际重构中,我经常把它们组合起来用:
- 先用引入解释变量,把复杂的条件或计算拆成有意义的变量。
- 再用提取方法,把一段独立的逻辑拎出来。
- 如果发现某个方法太简单且只被调用一次,就用内联方法把它塞回去。
说白了,这三个手法都是在做同一件事:让代码更容易被人类理解。编译器不在乎你的代码长什么样,但你的同事在乎,两周后的你自己也在乎。
总结一下:
| 手法 | 什么时候用 | 效果 |
|---|---|---|
| 提取方法 | 一段逻辑可以独立成方法 | 减少方法长度,提升复用性 |
| 内联方法 | 方法太简单且只被调用一次 | 减少间接层,提升阅读流畅度 |
| 引入解释变量 | 表达式复杂或条件判断冗长 | 提升可读性,降低理解成本 |
好了,今天的内容就到这里。下一章咱们接着聊重构基础手法(下),包括「拆分循环」「移动语句」「以查询取代临时变量」等。这些手法配合起来用,效果更佳。
记住一句话:好的代码,是改出来的,不是写出来的。