告别3000行if-else:核心链路重构的3个实战心法

两年前的一个周五下午,我正准备进行代码Review,监控群突然炸了。核心交易系统的CPU水位飙升到90%,排查后发现,罪魁祸首竟是一段长达3000行的PaymentService类。

那是典型的"面条代码":为了支持不同渠道(支付宝、微信、银联)、不同商户类型、不同营销活动的组合,前人堆砌了数不清的if-else嵌套。一位新来的同事只是想加一个小众渠道的立减逻辑,结果因为没看清第18层嵌套的逻辑,导致全渠道金额计算错误。

这不仅仅是代码丑陋的问题,这是架构的隐形债务。对于架构师和高级开发来说,复杂的if-else意味着高昂的认知成本、脆弱的扩展性和随时可能爆炸的线上雷。

很多教程只讲设计模式的UML图,却不讲"什么场景用什么"。今天,我想结合这几年拆解10+线上架构优化的经验,聊聊如何用设计模式真正驯服这些复杂的判断逻辑。

策略模式+Map:消灭"并列式"业务判断

在电商、支付、物流等领域,我们最常见的就是"根据类型A,执行逻辑B"的代码。

痛点场景: 2021年,我接手过一个营销发券系统。产品经理极富创意,每周都要搞新花样:满减券、折扣券、立减券、阶梯券…此时的代码大概长这样:

public void sendCoupon(User user, String type) {
    if ("DISCOUNT".equals(type)) {
        // 50行折扣券逻辑
    } else if ("CASH_OFF".equals(type)) {
        // 80行立减券逻辑
    } else if ("LADDER".equals(type)) {
        // 100行阶梯券逻辑
    } 
    // ...还有十几个else
}

每次上线新券种,都要修改主类,违反了开闭原则(OCP),且极易改坏旧逻辑。

重构方案:策略模式 + Spring Map注入

我们不需要手写工厂类,利用Spring的特性,可以将Bean Name作为Key,服务实例作为Value自动注入。

配图

  1. 定义接口CouponStrategy,包含calculate()send()方法。
  2. 具体实现DiscountStrategyCashOffStrategy分别实现接口,并标注@Component("DISCOUNT")
  3. Map路由
@Service
public class CouponService {
    // Spring会自动将所有实现类注入到这个Map中
    @Autowired
    private Map<String, CouponStrategy> strategyMap;

    public void sendCoupon(User user, String type) {
        CouponStrategy strategy = strategyMap.get(type);
        if (strategy == null) {
            throw new BusinessException("未知券类型");
        }
        strategy.send(user);
    }
}

实战结果: 重构后,新增一种券类型只需新增一个类,主流程代码零修改。我们在双11大促期间,临时上线了3种新玩法,开发耗时从原来的2天缩短到4小时,且因为代码物理隔离,完全不担心影响核心链路。

责任链模式:驯服"漏斗式"多重校验

另一种让人头秃的场景是复杂的校验逻辑,像漏斗一样层层过滤。

痛点场景: 在一个信贷审批系统中,用户发起借款前需要经过:黑名单校验、地域限制校验、额度校验、风控评分校验、产品状态校验…

原来的代码是这样的:

if (checkBlacklist(user)) {
    if (checkLocation(user)) {
        if (checkQuota(user)) {
             // 业务逻辑...
        } else {
             return "额度不足";
        }
    } else {
        return "地区不支持";
    }
} else {
    return "黑名单用户";
}

这种代码被称为"箭头型代码",不仅难读,而且难以调整顺序。如果运营要求"先把地区校验提到黑名单校验之前",开发人员可能要重构半天。

重构方案:责任链模式(Chain of Responsibility)

我们要把每一个校验逻辑封装成一个独立的"处理器(Handler)",然后像搭积木一样把它们串起来。

  1. 抽象基类:定义AbstractCheckHandler,包含nextHandler引用和check()方法。
  2. 链式组装:可以通过数据库配置或Spring Order注解来定义链条的顺序。
public abstract class AbstractCheckHandler {
    protected AbstractCheckHandler next;
    
    public void setNext(AbstractCheckHandler next) { this.next = next; }
    
    public Result filter(User user) {
        if (!doCheck(user)) {
            return fail();
        }
        if (next != null) {
            return next.filter(user);
        }
        return success();
    }
    
    protected abstract boolean doCheck(User user);
}

实战结果: 我们将7个硬编码的校验逻辑拆解为独立的Handler。最爽的是,我们将链条的组装逻辑放到了配置中心(Nacos)。 有一次,风控系统升级,需要临时下线"评分校验",运维人员直接在配置中心把该Handler的开关关掉,整个过程无需重新部署代码,真正实现了业务编排的灵活性。

状态模式:治理"流转式"复杂状态

在订单系统、工单系统或工作流引擎中,实体状态的流转控制往往是Bug的高发区。

痛点场景: 一个电商订单,有新建、支付中、已支付、发货、完成、退款中等状态。每个状态下能做的操作不同,比如"已发货"状态不能直接"取消",“退款中"不能"确认收货”。

如果用if-else判断:

public void cancelOrder(Order order) {
    if (order.getStatus() == PAID) {
        // 退款逻辑
    } else if (order.getStatus() == SHIPPED) {
        throw new RuntimeException("已发货不能取消");
    } else if (order.getStatus() == CREATED) {
        // 直接关闭
    }
    // ...
}

当状态超过5个,操作超过10个,这个类就会膨胀成上帝类,任何一个状态流转的改动都需要小心翼翼地搜索所有if

重构方案:状态模式(State Pattern)

将每个状态看作一个独立的类,把该状态下允许的行为封装进去。

  1. 状态接口OrderState,定义pay()ship()cancel()等行为。
  2. 具体状态类PaidStateShippedState。在ShippedState类的cancel()方法中直接抛出异常,而在PaidState中则执行退款逻辑。
  3. 上下文切换:Context类负责持有当前状态,并委托操作。

实战结果: 我们在重构物流中台的运单状态机时使用了此模式。当时业务方要求增加一个"异常挂起"状态,该状态下拦截所有操作。我们只新增了一个SuspendedState类,重写了所有方法返回"禁止操作"提示,就完成了需求,完全没有触碰核心调度逻辑,测试覆盖率达到了100%。

总结与落地工具

配图

重构不是为了炫技,而是为了降低熵增

什么时候该重构?我大概总结了一个简单的判断标准:

  • 重复度:当一段if-else逻辑在3个以上的地方重复出现;
  • 复杂度:当嵌套层级超过3层;
  • 易变度:当这段逻辑在过去一个月内被修改了2次以上。

最后,分享一个我团队内部使用的**“重构决策卡”**,你可以复制保存,下次遇到长代码时对照使用:

场景特征 推荐模式 核心收益
同级并列:不同类型执行不同逻辑(如支付渠道、优惠券) 策略模式 + Map 这里的else变成了新的Class,符合OCP原则,易扩展
顺序过滤:多重校验、黑白名单、规则引擎 责任链模式 逻辑解耦,支持动态编排和热插拔
状态流转:基于当前状态判断能否执行操作(如订单、审批) 状态模式 消除非法的状态流转,将复杂逻辑分散到各个状态类中
结构相同细节不同:主流程固定,子步骤有差异(如报表导出) 模板方法模式 复用骨架代码,减少重复开发

落地行动指南:

  1. 不要全盘重写:先挑一个痛点最明显的业务场景(比如改动最频繁的那个Service)。
  2. 先加测试:在动代码前,必须给现有的if-else逻辑加上单元测试,这是你的安全网。
  3. 小步快跑:抽离一个分支 -> 验证 -> 再抽离下一个。不要试图一次性上线完美的架构。

代码整洁度,某种程度上代表了团队的尊严。希望下次你打开IDE时,面对的不再是令人绝望的if-else迷宫,而是清晰优雅的架构森林。