接手 5 年二开旧系统?我用 3 步重构订单核心逻辑,新增功能只需加 1 个类

27 阅读13分钟

木鱼

木鱼

订单系统7层if-else重构实战

接手旧系统特别是那种5年前的二开系统有多烦神?相信二开程序员都有过类似经历。

前段时间接手了一个商城系统,其中订单系统的逻辑堪称“灾难现场”——7层嵌套的if-else加上零散的回调函数,代码像一碗缠绕的意大利面条,可读性差得姚明,代码编写的人都直挠头。更致命的是,新增一个“优惠券校验”功能,需要在5个不同的嵌套分支中修改代码,不仅效率低下,还时刻担心遗漏逻辑引发线上故障。

经过很长时间的代码分析后,我决定对这段核心代码进行重构。最终通过“抽象状态机+拆分处理器+责任链模式”的三步走方案,解决了代码臃肿、扩展困难、可读性差的问题(还发现了很多难发现的BUG),我有时候担心机器读这段程序会不会发疯。重构后,新增业务规则只需实现一个接口就可以了。

今天就把这次重构的完整思路、实现细节和踩坑经验分享给大家,希望能给面临类似问题的同学带来启发。

一、重构前:订单创建逻辑的“灾难现场”

在重构之前,我们先直面问题。订单创建的核心逻辑包含了状态判断(如是否已下单、是否超时)、多种业务规则校验(库存、风控、积分、价格计算)以及回调通知(如通知库存扣减、积分发放)。原始代码的大致结构如下(伪代码,因为代码太长了,有几万多行):

/**
 * 重构前的订单创建逻辑:7层if-else+回调嵌套
 */
public void createOrder(OrderDTO orderDTO, Callback callback) {
    // 第一层:判断订单状态
    if (orderDTO.getStatus() == OrderStatus.NOT_CREATED) {
        // 第二层:判断用户是否登录
        if (UserContext.isLogin()) {
            User user = getUserById(orderDTO.getUserId());
            // 第三层:判断用户是否黑名单
            if (!user.isBlackList()) {
                // 第四层:判断库存是否充足
                if (stockService.checkStock(orderDTO.getProductId(), orderDTO.getNum())) {
                    // 第五层:判断是否使用积分
                    if (orderDTO.getUsePoint()) {
                        // 第六层:判断积分是否足够
                        if (pointService.checkPoint(user.getId(), orderDTO.getPoint())) {
                            // 第七层:判断是否使用优惠券
                            if (orderDTO.getCouponId() != null) {
                                // 优惠券校验(新增功能需在此修改,这里还有优惠券不同会员等级使用的金额还不同,反正很啰嗦,中间可能需求发送了变更,还有很多类注释掉了。也没有注释或文档说明,看着头痛;)
                                boolean couponValid = couponService.checkCoupon(orderDTO.getCouponId(), user.getId());
                                if (couponValid) {
                                    // 扣减积分
                                    pointService.deductPoint(user.getId(), orderDTO.getPoint());
                                    // 扣减库存
                                    stockService.deductStock(orderDTO.getProductId(), orderDTO.getNum());
                                    // 计算最终价格(这里还有几个比较少的BUG,比如:四舍五入的问题,金额没有用bigdecimal类型,我服的是进入没有出问题)
                                    BigDecimal finalPrice = calculateFinalPrice(orderDTO);
                                    orderDTO.setFinalPrice(finalPrice);
                                    // 保存订单
                                    orderDAO.insert(orderDTO);
                                    // 回调通知成功
                                    callback.onSuccess(orderDTO.getOrderId());
                                } else {
                                    callback.onFailure("优惠券无效");
                                }
                            } else {
                                // 不使用优惠券的逻辑(新增功能需在此修改)
                                pointService.deductPoint(user.getId(), orderDTO.getPoint());
                                stockService.deductStock(orderDTO.getProductId(), orderDTO.getNum());
                                BigDecimal finalPrice = calculateFinalPrice(orderDTO);
                                orderDTO.setFinalPrice(finalPrice);
                                orderDAO.insert(orderDTO);
                                callback.onSuccess(orderDTO.getOrderId());
                            }
                        } else {
                            callback.onFailure("积分不足");
                        }
                    } else {
                        // 不使用积分的逻辑(新增功能需在此修改)
                        if (orderDTO.getCouponId() != null) {
                            boolean couponValid = couponService.checkCoupon(orderDTO.getCouponId(), user.getId());
                            if (couponValid) {
                                stockService.deductStock(orderDTO.getProductId(), orderDTO.getNum());
                                BigDecimal finalPrice = calculateFinalPrice(orderDTO);
                                orderDTO.setFinalPrice(finalPrice);
                                orderDAO.insert(orderDTO);
                                callback.onSuccess(orderDTO.getOrderId());
                            } else {
                                callback.onFailure("优惠券无效");
                            }
                        } else {
                            // 既不使用积分也不使用优惠券(新增功能需在此修改)
                            stockService.deductStock(orderDTO.getProductId(), orderDTO.getNum());
                            BigDecimal finalPrice = calculateFinalPrice(orderDTO);
                            orderDTO.setFinalPrice(finalPrice);
                            orderDAO.insert(orderDTO);
                            callback.onSuccess(orderDTO.getOrderId());
                        }
                    }
                } else {
                    callback.onFailure("库存不足");
                }
            } else {
                callback.onFailure("用户已被拉黑");
            }
        } else {
            callback.onFailure("用户未登录");
        }
    } else {
        callback.onFailure("订单状态异常");
    }
}

这段代码的问题显而易见:

1. 嵌套太深了,可读性差:7层(其实后面还有嵌套,但是没必要说了)if-else嵌套,代码缩进越来越多,我花费大量时间梳理逻辑流向,且没有文档(好像文档以前是有的,后来找不到了,大家应该也有这样的经历,只能边数理边写注释,最后总结文档,也是头痛),稍不注意就会看错分支。

2. 耦合严重:订单状态判断、用户校验、库存检查、积分扣减、优惠券校验等多个不同职责的逻辑混杂在一个方法中,修改一个逻辑可能影响其他功能。

3. 扩展困难,维护成本高:新增一个业务规则(如本文的“优惠券校验”),需要在多个嵌套分支中重复修改代码,不仅效率低,还极易遗漏,引发线上bug,有些bug很难测试出来。

4. 测试困难:由于分支过多且耦合严重,编写单元测试时需要构造大量不同的场景,导致测试成本高。

二、重构三步走:从混乱到清晰的架构设计

针对上述问题,我的核心重构思路是“解耦”——将不同职责的逻辑拆分隔离,通过设计模式串联流程,让代码结构清晰、扩展灵活。具体分为三步:

注:现在感觉清晰,其实在梳理的时候也是反复了几次;

  1. 抽象状态:用枚举定义订单状态及流转规则,解决状态判断混乱问题;

  2. 拆分处理器:将每个业务规则(库存、风控、积分、优惠券等)封装成独立Handler,实现职责单一;

  3. 统一入口:通过责任链模式将所有Handler串联起来,实现流程的统一调度和灵活扩展。

第一步:抽象状态机,规范订单状态流转

订单系统的核心是状态流转,重构的第一步就是将分散的状态判断抽象为状态机。通过枚举定义所有订单状态,并明确状态之间的合法流转关系,避免在代码中零散判断状态。

/**
 * 订单状态枚举(状态机核心)
 */
public enum OrderStatus {
    NOT_CREATED("未创建", Arrays.asList()),
    CREATED("已创建", Arrays.asList(NOT_CREATED)),
    PAID("已支付", Arrays.asList(CREATED)),
    CANCELLED("已取消", Arrays.asList(NOT_CREATED, CREATED)),
    FINISHED("已完成", Arrays.asList(PAID)),
    CLOSED("已关闭", Arrays.asList(CREATED, PAID));
    ......
    // 状态描述
    private final String desc;
    // 可流转至此状态的前置状态列表
    private final List<OrderStatus> allowPreStatus;

    OrderStatus(String desc, List<OrderStatus> allowPreStatus) {
        this.desc = desc;
        this.allowPreStatus = allowPreStatus;
    }

    /**
     * 校验状态流转是否合法
     * @param preStatus 前置状态
     * @return 合法返回true,否则false
     */
    public boolean isAllowTransition(OrderStatus preStatus) {
        return allowPreStatus.contains(preStatus);
    }
}

同时,创建订单上下文(OrderContext),统一存储订单创建过程中的所有数据(订单信息、用户信息、错误信息等),避免在多个方法之间传递大量参数。

/**
 * 订单处理上下文
 * 存储流程中的所有数据,统一传递
 */
public class OrderContext {
    // 订单信息
    private OrderDTO orderDTO;
    // 用户信息
    private User user;
    // 错误信息
    private String errorMsg;
    // 处理结果
    private boolean success = false;
    ......
    // getter、setter省略
}

通过状态机和上下文的抽象,首先解决了“状态判断混乱”和“参数传递繁琐”的问题。

第二步:拆分处理器,实现职责单一原则

重构的核心是“职责拆分”。我将订单创建流程中的每个业务规则都封装成一个独立的处理器(Handler),每个Handler只负责一件事,符合单一职责原则。首先定义一个统一的Handler接口:

/**
 * 订单处理器统一接口
 * 所有业务规则处理器都实现此接口
 */
public interface OrderHandler {
    /**
     * 处理核心逻辑
     * @param ctx 订单上下文
     */
    void handle(OrderContext ctx);

    /**
     * 判断当前处理器是否需要执行
     * 支持按需执行,提升效率
     * @param ctx 订单上下文
     * @return 需要执行返回true,否则false
     */
    default boolean needExecute(OrderContext ctx) {
        // 默认需要执行,子类可重写
        return true;
    }
       // ......
}

接口中定义了几个方法,其中两个方法:handle()用于处理核心业务逻辑,needExecute()用于判断当前处理器是否需要执行(默认需要,子类可根据场景重写)。接下来,将原始代码中的各个业务规则拆分为具体的Handler实现:

5. 用户登录校验处理器

//**
 * 用户登录校验处理器
 */
public class UserLoginHandler implements OrderHandler {
    @Override
    public void handle(OrderContext ctx) {
        // 若已存在错误信息,直接返回(责任链中断)
        if (ctx.getErrorMsg() != null) {
            return;
        }
        if (!UserContext.isLogin()) {
            ctx.setErrorMsg("用户未登录");
            ctx.setSuccess(false);
            return;
        }
        // 登录成功,查询用户信息存入上下文
        User user = getUserById(ctx.getOrderDTO().getUserId());
        ctx.setUser(user);
        ctx.setSuccess(true);
    }
    // ......
}

6. 用户黑名单校验处理器

/**
 * 用户黑名单校验处理器
 */
public class UserBlackListHandler implements OrderHandler {
    @Override
    public void handle(OrderContext ctx) {
        if (ctx.getErrorMsg() != null) {
            return;
        }
        User user = ctx.getUser();
        if (user == null) {
            ctx.setErrorMsg("用户信息获取失败");
            ctx.setSuccess(false);
            return;
        }
        if (user.isBlackList()) {
            ctx.setErrorMsg("用户已被拉黑");
            ctx.setSuccess(false);
            return;
        }
        ctx.setSuccess(true);
    }
}

7. 库存校验与扣减处理器

/**
 * 库存校验与扣减处理器
 */
public class StockHandler implements OrderHandler {
    @Autowired
    private StockService stockService;

    @Override
    public void handle(OrderContext ctx) {
        if (ctx.getErrorMsg() != null) {
            return;
        }
        OrderDTO orderDTO = ctx.getOrderDTO();
        // 校验库存
        boolean stockValid = stockService.checkStock(orderDTO.getProductId(), orderDTO.getNum());
        if (!stockValid) {
            ctx.setErrorMsg("库存不足");
            ctx.setSuccess(false);
            return;
        }
        // 扣减库存
        stockService.deductStock(orderDTO.getProductId(), orderDTO.getNum());
        ctx.setSuccess(true);
    }
}

8. 积分校验与扣减处理器(按业务需求执行)

/**
 * 积分校验与扣减处理器
 * 仅当用户使用积分时执行
 */
public class PointHandler implements OrderHandler {
    @Autowired
    private PointService pointService;

    @Override
    public void handle(OrderContext ctx) {
        if (ctx.getErrorMsg() != null) {
            return;
        }
        OrderDTO orderDTO = ctx.getOrderDTO();
        User user = ctx.getUser();
        // 校验积分
        boolean pointValid = pointService.checkPoint(user.getId(), orderDTO.getPoint());
        if (!pointValid) {
            ctx.setErrorMsg("积分不足");
            ctx.setSuccess(false);
            return;
        }
        // 扣减积分
        pointService.deductPoint(user.getId(), orderDTO.getPoint());
        ctx.setSuccess(true);
    }

    /**
     * 重写needExecute:仅当使用积分时执行
     */
    @Override
    public boolean needExecute(OrderContext ctx) {
        return ctx.getOrderDTO().getUsePoint();
    }
}

9. 优惠券校验处理器(新增功能,仅需新增Handler)

/**
 * 优惠券校验处理器
 * 新增功能,仅需实现OrderHandler接口
 */
public class CouponHandler implements OrderHandler {
    @Autowired
    private CouponService couponService;

    @Override
    public void handle(OrderContext ctx) {
        if (ctx.getErrorMsg() != null) {
            return;
        }
        OrderDTO orderDTO = ctx.getOrderDTO();
        User user = ctx.getUser();
        // 校验优惠券
        boolean couponValid = couponService.checkCoupon(orderDTO.getCouponId(), user.getId());
        if (!couponValid) {
            ctx.setErrorMsg("优惠券无效");
            ctx.setSuccess(false);
            return;
        }
        ctx.setSuccess(true);
    }

    /**
     * 重写needExecute:仅当使用优惠券时执行
     */
    @Override
    public boolean needExecute(OrderContext ctx) {
        return ctx.getOrderDTO().getCouponId() != null;
    }
}

10. 订单保存处理器

/**
 * 订单保存处理器
 */
public class OrderSaveHandler implements OrderHandler {
    @Autowired
    private OrderDAO orderDAO;

    @Override
    public void handle(OrderContext ctx) {
        if (ctx.getErrorMsg() != null) {
            return;
        }
        OrderDTO orderDTO = ctx.getOrderDTO();
        // 计算最终价格
        BigDecimal finalPrice = calculateFinalPrice(orderDTO);
        orderDTO.setFinalPrice(finalPrice);
        // 保存订单
        orderDAO.insert(orderDTO);
        ctx.setSuccess(true);
    }

    /**
     * 计算最终价格(原代码中的工具方法,提取至此)
     */
    private BigDecimal calculateFinalPrice(OrderDTO orderDTO) {
        // 价格计算逻辑
        ......
    }
}

通过这样的拆分,每个Handler只负责一个具体的业务规则,代码简洁清晰,职责明确。新增功能(如优惠券校验)时,只需新增一个Handler实现,无需修改任何原有代码,完美符合“开闭原则”。

第三步:责任链模式串联,实现统一入口与灵活调度

拆分出多个独立的Handler后,需要一个统一的方式来串联这些处理器,这就需要用到“责任链模式”。责任链模式可以将多个处理器组成一条链,请求沿着链传递,直到有处理器处理完或者链结束。我们创建一个Handler链的管理器,负责组装和执行所有Handler:

/**
 * 订单处理器责任链管理器
 * 负责组装Handler链并执行
 */
public class OrderHandlerChain {
    // 存储所有处理器(有序)
    private List<OrderHandler> handlerList = new ArrayList<>();

    /**
     * 添加处理器(支持链式调用)
     */
    public OrderHandlerChain addHandler(OrderHandler handler) {
        handlerList.add(handler);
        return this;
    }

    /**
     * 执行责任链
     */
    public void execute(OrderContext ctx) {
        // 校验订单状态流转是否合法(状态机的应用)
        OrderDTO orderDTO = ctx.getOrderDTO();
        OrderStatus targetStatus = OrderStatus.CREATED;
        if (!targetStatus.isAllowTransition(orderDTO.getStatus())) {
            ctx.setErrorMsg("订单状态异常");
            ctx.setSuccess(false);
            return;
        }

        // 遍历执行所有处理器
        for (OrderHandler handler : handlerList) {
            // 判断处理器是否需要执行
            if (handler.needExecute(ctx)) {
                handler.handle(ctx);
                // 若处理失败(存在错误信息),中断责任链
                if (!ctx.isSuccess()) {
                    break;
                }
            }
        }
    }
}

最后,创建订单服务的统一入口,通过责任链管理器组装Handler并执行:

/**
 * 订单服务(重构后的统一入口)
 */
@Service
public class OrderService {
    @Autowired
    private UserLoginHandler userLoginHandler;
    @Autowired
    private UserBlackListHandler userBlackListHandler;
    @Autowired
    private StockHandler stockHandler;
    @Autowired
    private PointHandler pointHandler;
    @Autowired
    private CouponHandler couponHandler;
    @Autowired
    private OrderSaveHandler orderSaveHandler;

    /**
     * 重构后的订单创建方法
     */
    public void createOrder(OrderDTO orderDTO, Callback callback) {
        // 1. 初始化上下文
        OrderContext ctx = new OrderContext();
        ctx.setOrderDTO(orderDTO);

        // 2. 组装责任链(按业务流程顺序添加Handler)
        OrderHandlerChain chain = new OrderHandlerChain()
                .addHandler(userLoginHandler)
                .addHandler(userBlackListHandler)
                .addHandler(stockHandler)
                .addHandler(pointHandler)
                .addHandler(couponHandler)
                .addHandler(orderSaveHandler);

        // 3. 执行责任链
        chain.execute(ctx);

        // 4. 回调结果
        if (ctx.isSuccess()) {
            callback.onSuccess(orderDTO.getOrderId());
        } else {
            callback.onFailure(ctx.getErrorMsg());
        }
    }
}

三、重构后:效果立竿见影,维护成本大幅降低

通过以上三步重构,订单创建逻辑的代码结构发生了翻天覆地的变化,带来的效果也十分显著:

代码可读性、可维护性大幅提升:消除了7层if-else嵌套,每个业务规则都封装在独立的Handler中,代码结构清晰,后续开发者只需看对应的Handler即可理解具体逻辑,无需梳理复杂的嵌套分支。

扩展灵活,新增功能成本极低:新增业务规则(如后续可能需要的“会员折扣校验”“风控规则升级”等),只需新增一个Handler实现,在责任链中添加即可,无需修改任何原有代码,彻底解决了“新增功能改多处”的痛点。

单元测试覆盖率显著提升:由于每个Handler职责单一,可单独编写单元测试,无需构造复杂的嵌套场景。重构后单元测试覆盖率从30%提升至85%,极大地提升了代码的可靠性。

故障定位更高效:每个Handler只负责一个功能,若出现问题,可快速定位到对应的Handler,无需在冗长的嵌套代码中排查。

四、重构过程中的踩坑经验与注意事项

这次重构虽然效果显著,但过程中也踩了一些坑,总结几点注意事项,希望能帮大家避坑:

Handler的执行顺序至关重要:责任链中Handler的添加顺序必须严格遵循业务流程(如先校验登录,再校验黑名单,最后扣减库存),否则会出现逻辑错误(如未校验用户就扣减库存)。建议在责任链管理器中添加注释,明确流程顺序。

上下文的设计要全面:OrderContext需要包含流程中所有需要传递的数据,避免后续扩展时频繁修改Context的字段。同时,Context中的错误信息、处理结果等状态要设计清晰,确保Handler之间的状态传递准确。

合理使用needExecute方法:对于一些“按需执行”的Handler(如积分、优惠券),通过needExecute方法过滤,可避免不必要的执行,提升系统性能。

重构需循序渐进,避免大拆大改:如果旧系统逻辑特别复杂,不建议一次性全部重构,我就是反复几次才最终完成的,大改回花费很多时间成本,最重要的的是需要一个业务全面精通的产品经理。可以先对核心分支进行重构,逐步替换旧代码,同时保留旧逻辑作为过渡,待重构代码稳定后再删除旧逻辑,降低线上风险。

充分测试,覆盖所有场景:重构后必须对所有业务场景进行充分测试,尤其是边界场景(如状态异常、参数为空、各种校验失败的场景),确保重构后的代码功能与原代码一致。

五、总结:重构的核心是“解耦”与“抽象”

回顾这次订单系统的重构,核心思路其实就是“解耦”与“抽象”:通过抽象状态机解决状态判断混乱的问题,通过拆分Handler实现职责解耦,通过责任链模式实现流程解耦。最终让代码从“意大利面条”变成“模块化组件”,实现了可扩展、可维护、可测试的目标。

对于很多旧系统来说,“嵌套if-else+回调”是常见的问题,背后反映的是最初开发时“重功能实现,轻架构设计”的问题,这个现象和早期产品是通过砌墙逐渐产生的,也就是今天增加一个逻辑,明天增加一个功能逐步堆起来的。而重构的价值,不仅仅是让代码变得整洁,更重要的是降低后续的维护成本和扩展成本,让系统能够适应业务的快速变化。

如果你也正在接手一个“难维护”的旧系统,不妨尝试用“抽象+拆分+设计模式”的思路进行重构。也许一开始会花费一些时间,但长期来看,绝对是值得的。

最后,如果你在类似的重构场景中有其他经验,或者有疑问,欢迎在评论区交流讨论!

----- 木鱼原创 首发知乎-----