订单系统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. 测试困难:由于分支过多且耦合严重,编写单元测试时需要构造大量不同的场景,导致测试成本高。
二、重构三步走:从混乱到清晰的架构设计
针对上述问题,我的核心重构思路是“解耦”——将不同职责的逻辑拆分隔离,通过设计模式串联流程,让代码结构清晰、扩展灵活。具体分为三步:
注:现在感觉清晰,其实在梳理的时候也是反复了几次;
-
抽象状态:用枚举定义订单状态及流转规则,解决状态判断混乱问题;
-
拆分处理器:将每个业务规则(库存、风控、积分、优惠券等)封装成独立Handler,实现职责单一;
-
统一入口:通过责任链模式将所有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+回调”是常见的问题,背后反映的是最初开发时“重功能实现,轻架构设计”的问题,这个现象和早期产品是通过砌墙逐渐产生的,也就是今天增加一个逻辑,明天增加一个功能逐步堆起来的。而重构的价值,不仅仅是让代码变得整洁,更重要的是降低后续的维护成本和扩展成本,让系统能够适应业务的快速变化。
如果你也正在接手一个“难维护”的旧系统,不妨尝试用“抽象+拆分+设计模式”的思路进行重构。也许一开始会花费一些时间,但长期来看,绝对是值得的。
最后,如果你在类似的重构场景中有其他经验,或者有疑问,欢迎在评论区交流讨论!
----- 木鱼原创 首发知乎-----