重构基础手法(上):提取方法、内联方法、引入解释变量

各位同学,咱们今天聊点实在的。

重构这件事,说白了就是「改代码但不改功能」。听起来简单,做起来门道不少。我刚开始带团队那会儿,最头疼的就是看到一段几百行的方法,逻辑全揉在一起,改一个 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 好得多。

三者如何配合使用?

这三个手法不是孤立的。在实际重构中,我经常把它们组合起来用:

  1. 先用引入解释变量,把复杂的条件或计算拆成有意义的变量。
  2. 再用提取方法,把一段独立的逻辑拎出来。
  3. 如果发现某个方法太简单且只被调用一次,就用内联方法把它塞回去。

说白了,这三个手法都是在做同一件事:让代码更容易被人类理解。编译器不在乎你的代码长什么样,但你的同事在乎,两周后的你自己也在乎。

总结一下:

手法 什么时候用 效果
提取方法 一段逻辑可以独立成方法 减少方法长度,提升复用性
内联方法 方法太简单且只被调用一次 减少间接层,提升阅读流畅度
引入解释变量 表达式复杂或条件判断冗长 提升可读性,降低理解成本

好了,今天的内容就到这里。下一章咱们接着聊重构基础手法(下),包括「拆分循环」「移动语句」「以查询取代临时变量」等。这些手法配合起来用,效果更佳。

记住一句话:好的代码,是改出来的,不是写出来的。