你最近是不是也遇到过这种情况辛辛苦苦在代码里实现了一个功能比如一个复杂的业务逻辑处理类或者一个网络请求的封装工具。当你把它交给团队其他成员或者过几个月自己再回来看时面对着一堆命名随意、职责混乱、动辄几百行的“巨无霸”方法脑子里只有一个念头“这坨恶心的东西你管它叫‘类’”这不仅仅是代码美观问题。这样的代码是项目里的“定时炸弹”它难以理解导致新人上手成本极高它难以修改任何一点需求变动都可能引发意想不到的连锁 Bug它更难以测试因为你根本不知道从哪里下手去覆盖那些盘根错节的逻辑。最终团队陷入“开发-还债-再开发”的恶性循环技术债越垒越高。今天我们不谈那些高深莫测的设计模式理论就从最务实、最痛的点出发解决一个具体问题如何把一个臃肿、混乱的“上帝类”或“巨无霸方法”重构得清晰、健壮且易于维护。本文将以一个模拟的、但极其典型的“订单处理服务”为例手把手带你走完一次完整的重构实战。你会看到好的设计不是炫技而是让代码自己会说话让后续的每一次扩展都变得轻松自然。1. 我们面对的是什么一坨典型的“坏代码”在开始动手之前我们必须先诊断“病情”。下面这段代码就是一个典型的、需要被重构的“病人”。它试图在一个类里完成订单创建的所有事情参数校验、价格计算、库存检查、优惠券核销、订单保存、日志记录…… 几乎所有你能想到的环节都塞在了一起。// 文件路径src/main/java/com/example/badcode/OrderService.java /** * 糟糕的订单服务示例 - 重构前的“上帝类” */ Service public class BadOrderService { Autowired private OrderRepository orderRepository; Autowired private ProductRepository productRepository; Autowired private CouponRepository couponRepository; Autowired private InventoryService inventoryService; Autowired private EmailService emailService; /** * 创建订单 - 一个做了所有事情的方法 * param userId 用户ID * param productId 商品ID * param quantity 数量 * param couponCode 优惠码 * return 订单ID */ public Long createOrder(Long userId, Long productId, Integer quantity, String couponCode) { // 1. 基础参数校验 if (userId null || userId 0) { throw new IllegalArgumentException(用户ID无效); } if (productId null || productId 0) { throw new IllegalArgumentException(商品ID无效); } if (quantity null || quantity 0) { throw new IllegalArgumentException(购买数量无效); } System.out.println(参数校验通过); // 2. 查询商品信息 Product product productRepository.findById(productId); if (product null) { throw new RuntimeException(商品不存在); } if (!product.getStatus().equals(ON_SALE)) { throw new RuntimeException(商品已下架); } // 3. 检查库存 boolean hasStock inventoryService.checkStock(productId, quantity); if (!hasStock) { throw new RuntimeException(商品库存不足); } // 4. 计算原始价格 BigDecimal unitPrice product.getPrice(); BigDecimal originalPrice unitPrice.multiply(new BigDecimal(quantity)); // 5. 优惠券处理 BigDecimal finalPrice originalPrice; if (couponCode ! null !couponCode.trim().isEmpty()) { Coupon coupon couponRepository.findByCode(couponCode); if (coupon null) { throw new RuntimeException(优惠券不存在); } if (!coupon.getUserId().equals(userId)) { throw new RuntimeException(优惠券不属于当前用户); } if (coupon.getExpiredTime().before(new Date())) { throw new RuntimeException(优惠券已过期); } if (originalPrice.compareTo(coupon.getMinOrderAmount()) 0) { throw new RuntimeException(订单金额未达到优惠券使用门槛); } // 计算优惠后价格 if (PERCENT.equals(coupon.getType())) { BigDecimal discount originalPrice.multiply(coupon.getValue().divide(new BigDecimal(100))); finalPrice originalPrice.subtract(discount); } else if (FIXED.equals(coupon.getType())) { finalPrice originalPrice.subtract(coupon.getValue()); } // 标记优惠券为已使用 coupon.setUsed(true); couponRepository.save(coupon); System.out.println(优惠券[ couponCode ]已使用); } // 确保最终价格不为负数 if (finalPrice.compareTo(BigDecimal.ZERO) 0) { finalPrice BigDecimal.ZERO; } // 6. 扣减库存 inventoryService.reduceStock(productId, quantity); // 7. 创建订单对象并保存 Order order new Order(); order.setUserId(userId); order.setProductId(productId); order.setQuantity(quantity); order.setUnitPrice(unitPrice); order.setOriginalPrice(originalPrice); order.setDiscount(finalPrice.subtract(originalPrice).abs()); order.setFinalPrice(finalPrice); order.setStatus(CREATED); order.setCreateTime(new Date()); Order savedOrder orderRepository.save(order); System.out.println(订单创建成功ID: savedOrder.getId()); // 8. 发送邮件通知模拟 try { emailService.sendOrderConfirmation(userId, savedOrder.getId()); } catch (Exception e) { // 邮件发送失败不应该影响主流程只记录日志 System.err.println(发送订单确认邮件失败: e.getMessage()); } // 9. 记录操作日志模拟 System.out.println(用户[ userId ]创建了订单[ savedOrder.getId() ]金额 finalPrice); return savedOrder.getId(); } }这段代码的问题简直是一本“反面教材大全”单一职责原则SRP彻底失效一个方法长达百行承担了校验、查询、计算、存储、通知、日志等近10个职责。难以测试要测试这个方法你需要 mock 多个 Repository 和 Service构造各种分支条件有无优惠券、优惠券类型、库存不足等测试用例会极其庞大且脆弱。事务边界模糊扣减库存、保存订单、更新优惠券状态这些操作应该在一个事务里吗如果邮件发送失败订单应该回滚吗代码没有给出清晰答案。错误处理粗糙到处是RuntimeException调用方无法区分是业务错误还是系统错误。邮件发送失败被“吞掉”了这可能在生产环境导致重大问题不被察觉。可读性差逻辑层层嵌套没有抽象。三个月后没人记得清楚优惠券计算的细节。难以扩展如果想增加一种新的优惠券类型比如“满减”或者增加支付环节你必须直接修改这个核心方法风险极高。接下来我们的目标就是将这坨“坏代码”重构成一组职责清晰、易于协作的“好代码”。2. 重构的核心武器识别代码的“坏味道”在动手写新代码之前我们必须学会识别代码中的“坏味道”Code Smells。这是重构的起点。针对上面的BadOrderService我们可以系统地找出以下问题坏味道类型在示例中的体现带来的风险过长方法createOrder方法超过80行逻辑混杂。难以理解、测试和维护。一个改动可能影响多处。过大类该类虽然目前方法不多但已注入过多依赖预示它会不断膨胀。类职责模糊内聚性低。重复代码参数校验、异常抛出模式有重复趋势。修改时需要找到所有重复处容易遗漏。基本类型偏执使用Long userId,BigDecimal price等基本类型到处传递。缺乏业务含义容易用错。例如把userId当成orderId传递。数据泥团订单创建的参数userId, productId, quantity, couponCode总是同时出现。应该被封装成一个值对象如CreateOrderCommand。依恋情结方法对Product、Coupon的内部状态如getType(),getValue()知道得太多。业务逻辑分散一旦Coupon的计算规则变化需要修改多处。过多的注释需要用注释来解释代码块在做什么如“// 5. 优惠券处理”。说明代码本身不够清晰应该用方法名来替代注释。不清晰的错误处理混用IllegalArgumentException和RuntimeException且错误信息模糊。调用方无法进行精准的错误恢复或提示。识别出这些坏味道我们重构的方向就明确了分解、封装、抽象、明确。3. 重构第一步搭建安全网与创建测试重构的第一原则是不改变代码的外部行为。为了确保重构过程中不引入 Bug我们必须先为原有代码编写测试。这些测试将成为我们的“安全网”。由于原代码难以测试我们可以先为其编写一个集成测试验证主要的成功路径和几个关键的异常路径。// 文件路径src/test/java/com/example/badcode/OrderServiceTest.java SpringBootTest Transactional // 测试后数据回滚 class BadOrderServiceTest { Autowired private BadOrderService orderService; Autowired private ProductRepository productRepository; Autowired private CouponRepository couponRepository; MockBean // 模拟外部依赖 private InventoryService inventoryService; MockBean private EmailService emailService; private Product testProduct; private Coupon validCoupon; BeforeEach void setUp() { // 准备测试数据 testProduct new Product(); testProduct.setId(1L); testProduct.setName(测试商品); testProduct.setPrice(new BigDecimal(100.00)); testProduct.setStatus(ON_SALE); productRepository.save(testProduct); validCoupon new Coupon(); validCoupon.setId(1L); validCoupon.setCode(TEST100); validCoupon.setUserId(1001L); validCoupon.setType(FIXED); validCoupon.setValue(new BigDecimal(10.00)); validCoupon.setMinOrderAmount(new BigDecimal(50.00)); validCoupon.setExpiredTime(Date.from(Instant.now().plus(7, ChronoUnit.DAYS))); validCoupon.setUsed(false); couponRepository.save(validCoupon); // 模拟库存服务行为 when(inventoryService.checkStock(eq(1L), anyInt())).thenReturn(true); doNothing().when(inventoryService).reduceStock(eq(1L), anyInt()); } Test void testCreateOrderSuccess() { // 执行 Long orderId orderService.createOrder(1001L, 1L, 2, TEST100); // 验证 assertNotNull(orderId); // 可以进一步验证订单数据是否正确存入数据库 } Test void testCreateOrderFail_WhenProductNotExist() { // 验证当商品不存在时应抛出异常 assertThrows(RuntimeException.class, () - { orderService.createOrder(1001L, 999L, 1, null); }); } Test void testCreateOrderFail_WhenNoStock() { // 模拟库存不足 when(inventoryService.checkStock(eq(1L), anyInt())).thenReturn(false); assertThrows(RuntimeException.class, () - { orderService.createOrder(1001L, 1L, 10, null); }); } }有了这个测试套件我们就可以开始大胆重构了。每次重构一小步然后运行测试确保所有测试依然通过。4. 重构实战从“上帝类”到清晰领域模型我们的重构将分步进行每一步都解决一个具体问题并保持代码可编译、测试可通过。4.1 步骤一引入命令对象封装输入参数首先解决“数据泥团”问题。将分散的参数封装成一个CreateOrderCommand值对象。这不仅能提高代码可读性也便于后续的参数校验和传递。// 文件路径src/main/java/com/example/goodcode/command/CreateOrderCommand.java import lombok.Data; import javax.validation.constraints.NotNull; import javax.validation.constraints.Positive; /** * 创建订单的命令对象 * 使用 Lombok Data 自动生成 getter, setter 等方法 * 使用 JSR-303 注解进行声明式校验 */ Data public class CreateOrderCommand { NotNull(message 用户ID不能为空) Positive(message 用户ID必须为正数) private Long userId; NotNull(message 商品ID不能为空) Positive(message 商品ID必须为正数) private Long productId; NotNull(message 购买数量不能为空) Positive(message 购买数量必须为正数) private Integer quantity; private String couponCode; // 优惠码可为空 // 可以添加简单的业务校验方法 public void validateBusiness() { // 未来可以扩展更复杂的业务规则校验 } }4.2 步骤二提炼校验器单一职责将参数校验逻辑从主方法中剥离出来形成一个独立的Validator组件。这符合单一职责原则也让校验逻辑可以被复用和单独测试。// 文件路径src/main/java/com/example/goodcode/validator/OrderValidator.java import com.example.goodcode.command.CreateOrderCommand; import com.example.goodcode.exception.BusinessException; import com.example.goodcode.model.Product; import org.springframework.stereotype.Component; import java.math.BigDecimal; /** * 订单业务校验器 */ Component public class OrderValidator { public void validateCommand(CreateOrderCommand command) { if (command null) { throw new BusinessException(订单创建命令不能为空); } // JSR-303 会处理基础校验这里主要处理业务校验 // 例如购买数量是否超过限购等 } public void validateProduct(Product product) { if (product null) { throw new BusinessException(商品不存在); } if (!ON_SALE.equals(product.getStatus())) { throw new BusinessException(商品[ product.getName() ]已下架); } } public void validateStock(Long productId, Integer quantity, boolean hasStock) { if (!hasStock) { throw new BusinessException(商品库存不足); } } public void validatePrice(BigDecimal finalPrice) { if (finalPrice.compareTo(BigDecimal.ZERO) 0) { throw new BusinessException(订单最终价格计算异常); } } }同时我们定义清晰的业务异常取代模糊的RuntimeException。// 文件路径src/main/java/com/example/goodcode/exception/BusinessException.java /** * 业务异常 */ public class BusinessException extends RuntimeException { private String code; // 可定义错误码 public BusinessException(String message) { super(message); } // 可扩展构造器支持错误码和原因 }4.3 步骤三抽象价格计算策略原方法中优惠券计算逻辑直接嵌在主干流程里并且对Coupon对象的内部细节type,value有很强的“依恋情结”。我们使用策略模式将其抽象出来。首先定义价格计算上下文和策略接口// 文件路径src/main/java/com/example/goodcode/price/PriceCalculateContext.java import lombok.Data; import java.math.BigDecimal; /** * 价格计算上下文 */ Data public class PriceCalculateContext { private BigDecimal originalPrice; // 原始价格 private String couponCode; // 优惠码 // 可以扩展其他上下文信息如用户等级、商品分类等 }// 文件路径src/main/java/com/example/goodcode/price/calculator/PriceCalculator.java import com.example.goodcode.price.PriceCalculateContext; import java.math.BigDecimal; /** * 价格计算策略接口 */ public interface PriceCalculator { /** * 是否支持当前计算上下文 */ boolean support(PriceCalculateContext context); /** * 计算最终价格 * param context 计算上下文 * return 计算后的价格 */ BigDecimal calculate(PriceCalculateContext context); }然后实现具体的计算策略例如“无优惠”、“百分比折扣”、“固定金额折扣”// 文件路径src/main/java/com/example/goodcode/price/calculator/NoDiscountCalculator.java import com.example.goodcode.price.PriceCalculateContext; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.math.BigDecimal; /** * 无优惠计算器默认 */ Component Order(100) // 优先级最低 public class NoDiscountCalculator implements PriceCalculator { Override public boolean support(PriceCalculateContext context) { // 没有优惠码或者优惠码为空时使用此计算器 return context.getCouponCode() null || context.getCouponCode().trim().isEmpty(); } Override public BigDecimal calculate(PriceCalculateContext context) { // 直接返回原价 return context.getOriginalPrice(); } }// 文件路径src/main/java/com/example/goodcode/price/calculator/PercentDiscountCalculator.java import com.example.goodcode.price.PriceCalculateContext; import com.example.goodcode.model.Coupon; import com.example.goodcode.repository.CouponRepository; import com.example.goodcode.exception.BusinessException; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.util.Date; /** * 百分比折扣计算器 */ Component Order(10) // 优先级较高 public class PercentDiscountCalculator implements PriceCalculator { private final CouponRepository couponRepository; public PercentDiscountCalculator(CouponRepository couponRepository) { this.couponRepository couponRepository; } Override public boolean support(PriceCalculateContext context) { if (context.getCouponCode() null) return false; Coupon coupon couponRepository.findByCode(context.getCouponCode()); return coupon ! null PERCENT.equals(coupon.getType()); } Override public BigDecimal calculate(PriceCalculateContext context) { Coupon coupon couponRepository.findByCode(context.getCouponCode()); validateCoupon(coupon, context); BigDecimal discount context.getOriginalPrice() .multiply(coupon.getValue().divide(new BigDecimal(100))); BigDecimal finalPrice context.getOriginalPrice().subtract(discount); // 标记优惠券已使用注意实际应在事务成功后执行 coupon.setUsed(true); couponRepository.save(coupon); return finalPrice.max(BigDecimal.ZERO); // 确保价格非负 } private void validateCoupon(Coupon coupon, PriceCalculateContext context) { // 优惠券基础校验可以抽到公共方法或另一个校验器 if (coupon.getExpiredTime().before(new Date())) { throw new BusinessException(优惠券已过期); } if (context.getOriginalPrice().compareTo(coupon.getMinOrderAmount()) 0) { throw new BusinessException(订单金额未达到优惠券使用门槛); } // ... 其他校验 } }固定折扣计算器FixedAmountCalculator类似此处省略最后创建一个工厂或服务来选择合适的策略// 文件路径src/main/java/com/example/goodcode/price/PriceCalculateService.java import com.example.goodcode.price.PriceCalculateContext; import com.example.goodcode.price.calculator.PriceCalculator; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.List; /** * 价格计算服务 */ Service public class PriceCalculateService { private final ListPriceCalculator calculators; // Spring 会自动注入所有实现了 PriceCalculator 的 Bean public PriceCalculateService(ListPriceCalculator calculators) { this.calculators calculators; } public BigDecimal calculateFinalPrice(PriceCalculateContext context) { return calculators.stream() .filter(calculator - calculator.support(context)) .findFirst() .orElseThrow(() - new BusinessException(未找到合适的价格计算策略)) .calculate(context); } }4.4 步骤四定义清晰的服务层与领域模型现在我们可以重新构建一个清晰、职责单一的OrderService。它的职责是协调各个领域组件校验器、计算器、库存服务、仓储来完成订单创建这个业务流程。// 文件路径src/main/java/com/example/goodcode/service/OrderService.java import com.example.goodcode.command.CreateOrderCommand; import com.example.goodcode.model.Order; import com.example.goodcode.model.Product; import com.example.goodcode.price.PriceCalculateContext; import com.example.goodcode.price.PriceCalculateService; import com.example.goodcode.repository.OrderRepository; import com.example.goodcode.repository.ProductRepository; import com.example.goodcode.validator.OrderValidator; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; /** * 重构后的订单服务 - 职责清晰协调各方 */ Slf4j Service RequiredArgsConstructor // Lombok 生成包含 final 字段的构造器 public class OrderService { private final OrderValidator orderValidator; private final ProductRepository productRepository; private final InventoryService inventoryService; private final PriceCalculateService priceCalculateService; private final OrderRepository orderRepository; private final NotificationService notificationService; /** * 创建订单 - 重构后的主方法 * param command 创建订单命令 * return 订单ID */ Transactional(rollbackFor Exception.class) // 明确事务边界任何异常都回滚 public Long createOrder(CreateOrderCommand command) { // 1. 参数与基础业务校验 orderValidator.validateCommand(command); // 2. 获取并校验商品 Product product productRepository.findById(command.getProductId()) .orElseThrow(() - new BusinessException(商品不存在)); orderValidator.validateProduct(product); // 3. 检查库存 boolean hasStock inventoryService.checkStock(command.getProductId(), command.getQuantity()); orderValidator.validateStock(command.getProductId(), command.getQuantity(), hasStock); // 4. 计算原始价格 BigDecimal originalPrice product.getPrice().multiply(BigDecimal.valueOf(command.getQuantity())); // 5. 构建计算上下文并计算最终价格 PriceCalculateContext context new PriceCalculateContext(); context.setOriginalPrice(originalPrice); context.setCouponCode(command.getCouponCode()); BigDecimal finalPrice priceCalculateService.calculateFinalPrice(context); orderValidator.validatePrice(finalPrice); // 6. 扣减库存在事务内 inventoryService.reduceStock(command.getProductId(), command.getQuantity()); // 7. 创建并保存订单实体 Order order Order.create(command, product, originalPrice, finalPrice); Order savedOrder orderRepository.save(order); log.info(订单创建成功ID: {}, 用户: {}, 金额: {}, savedOrder.getId(), command.getUserId(), finalPrice); // 8. 发送通知在事务提交后异步执行避免影响主流程 notificationService.sendOrderCreatedNotification(savedOrder); return savedOrder.getId(); } }注意我们将发送邮件的逻辑移到了一个NotificationService中并可能改为异步执行确保核心业务流程不被阻塞。// 文件路径src/main/java/com/example/goodcode/service/NotificationService.java import com.example.goodcode.model.Order; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; /** * 通知服务 */ Slf4j Service RequiredArgsConstructor public class NotificationService { private final EmailService emailService; Async // 异步执行 public void sendOrderCreatedNotification(Order order) { try { emailService.sendOrderConfirmation(order.getUserId(), order.getId()); log.info(已发送订单确认邮件订单ID: {}, order.getId()); } catch (Exception e) { // 异步任务中的异常需要单独处理可以记录日志并告警但不能影响主事务 log.error(发送订单确认邮件失败订单ID: {}, order.getId(), e); // 可以接入监控告警系统 } } }4.5 步骤五充实领域模型封装业务逻辑真正的领域驱动设计DDD中业务逻辑应尽可能封装在领域实体Entity或值对象Value Object中。我们让Order实体自己负责一部分创建逻辑。// 文件路径src/main/java/com/example/goodcode/model/Order.java import com.example.goodcode.command.CreateOrderCommand; import lombok.Data; import javax.persistence.*; import java.math.BigDecimal; import java.util.Date; /** * 订单领域实体 */ Data Entity Table(name t_order) public class Order { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private Long userId; private Long productId; private Integer quantity; private BigDecimal unitPrice; private BigDecimal originalPrice; private BigDecimal discount; private BigDecimal finalPrice; private String status; private Date createTime; // 私有构造器强制使用工厂方法创建 private Order() {} /** * 创建订单的工厂方法领域逻辑 * 将创建实体的复杂逻辑封装在此 */ public static Order create(CreateOrderCommand command, Product product, BigDecimal originalPrice, BigDecimal finalPrice) { Order order new Order(); order.setUserId(command.getUserId()); order.setProductId(command.getProductId()); order.setQuantity(command.getQuantity()); order.setUnitPrice(product.getPrice()); order.setOriginalPrice(originalPrice); order.setDiscount(originalPrice.subtract(finalPrice).abs()); // 计算折扣金额 order.setFinalPrice(finalPrice); order.setStatus(CREATED); order.setCreateTime(new Date()); return order; } // 可以在此添加其他领域方法如 order.cancel(), order.pay() 等 }5. 重构前后对比从“一坨”到“一组”让我们直观地感受一下重构带来的变化维度重构前 (BadOrderService)重构后 (新架构)核心方法行数100 行~30 行职责一个方法做所有事服务层协调逻辑分散到 Validator, Calculator, Entity 等可测试性极差需要 Mock 所有依赖构造复杂场景极好每个组件可独立单元测试服务层集成测试简单可读性需要逐行阅读逻辑嵌套深方法名即注释流程清晰如流水线可维护性修改优惠券逻辑需动核心方法风险高修改优惠券逻辑只需新增或修改一个PriceCalculator实现可扩展性添加新功能如积分抵扣必须修改原方法添加新功能只需实现新策略并在服务中注入错误处理模糊的RuntimeException清晰的BusinessException及错误码事务管理隐式边界不清通过Transactional注解显式声明6. 编写新的测试验证重构效果重构完成后我们需要为新代码编写更精细、更全面的测试。// 文件路径src/test/java/com/example/goodcode/service/OrderServiceTest.java SpringBootTest Transactional class OrderServiceTest { Autowired private OrderService orderService; Autowired private ProductRepository productRepository; MockBean private InventoryService inventoryService; MockBean private PriceCalculateService priceCalculateService; // 模拟价格计算 Test void createOrder_Success_ShouldReturnOrderId() { // Given CreateOrderCommand command new CreateOrderCommand(); command.setUserId(1001L); command.setProductId(1L); command.setQuantity(2); command.setCouponCode(null); Product product new Product(); product.setId(1L); product.setPrice(new BigDecimal(50.0)); productRepository.save(product); when(inventoryService.checkStock(anyLong(), anyInt())).thenReturn(true); when(priceCalculateService.calculateFinalPrice(any())).thenReturn(new BigDecimal(100.0)); // When Long orderId orderService.createOrder(command); // Then assertNotNull(orderId); verify(inventoryService).reduceStock(eq(1L), eq(2)); // 可以进一步验证数据库中的订单数据 } Test void createOrder_WhenProductOffSale_ShouldThrowException() { // Given CreateOrderCommand command new CreateOrderCommand(); command.setUserId(1001L); command.setProductId(1L); command.setQuantity(1); Product product new Product(); product.setId(1L); product.setStatus(OFF_SALE); // 已下架 productRepository.save(product); // When Then assertThrows(BusinessException.class, () - orderService.createOrder(command)); } }// 文件路径src/test/java/com/example/goodcode/price/calculator/PercentDiscountCalculatorTest.java SpringBootTest class PercentDiscountCalculatorTest { Autowired private PercentDiscountCalculator calculator; MockBean private CouponRepository couponRepository; Test void calculate_WithValidPercentCoupon_ShouldReturnCorrectPrice() { // Given PriceCalculateContext context new PriceCalculateContext(); context.setOriginalPrice(new BigDecimal(200.00)); context.setCouponCode(TEST10PERCENT); Coupon coupon new Coupon(); coupon.setType(PERCENT); coupon.setValue(new BigDecimal(10)); // 10% 折扣 coupon.setMinOrderAmount(new BigDecimal(100.00)); coupon.setExpiredTime(Date.from(Instant.now().plus(1, ChronoUnit.DAYS))); when(couponRepository.findByCode(TEST10PERCENT)).thenReturn(coupon); // When BigDecimal finalPrice calculator.calculate(context); // Then assertEquals(0, new BigDecimal(180.00).compareTo(finalPrice)); // 200 * 0.9 180 } }7. 常见问题与排查思路在重构或使用新架构时你可能会遇到以下问题问题现象可能原因排查方式解决方案启动报错No qualifying bean of type ‘PriceCalculator‘未将PriceCalculator的实现类声明为 Spring Bean (Component)。检查所有PriceCalculator实现类是否有Component注解。为策略实现类添加Component或Service注解。价格计算策略不生效总是走默认策略1.support方法逻辑有误。2. 策略 Bean 的加载顺序问题。1. 调试support方法。2. 检查Order注解值数值越小优先级越高。修正support方法逻辑或调整Order注解。事务未回滚1. 异常未被抛出。2. 异常类型不是RuntimeException或Error。3. 方法不是public。1. 检查代码中是否有try-catch吞掉异常。2. 确认Transactional(rollbackForException.class)。3. 确认方法为public。确保业务异常被抛出配置正确的rollbackFor方法设为public。异步通知未发送1. 未开启异步支持 (EnableAsync)。2. 异步方法在同一个类内调用。1. 检查启动类是否有EnableAsync。2. 检查是否通过this.sendNotification()调用。1. 添加EnableAsync。2. 通过代理对象调用异步方法或注入NotificationService自身。单元测试无法注入依赖测试类未使用SpringBootTest或ExtendWith(SpringExtension.class)。检查测试类的注解。为集成测试添加SpringBootTest为纯单元测试使用ExtendWith(MockitoExtension.class)并手动构造对象。8. 最佳实践与工程建议小步快跑频繁测试不要试图一次性重构整个系统。每次只重构一个明确的小问题如提取一个方法、封装一个对象然后立即运行测试。优先编写测试对于遗留代码如果难以编写单元测试可以先为其编写一个粗粒度的集成测试作为重构的“安全网”。识别并封装变化点像价格计算、支付方式、通知渠道这些容易变化的部分要第一时间用策略模式、工厂模式等抽象出来。明确事务边界使用Transactional清晰地标注哪些操作需要在一个事务内完成。对于发短信、邮件等外部调用考虑放在事务提交后异步执行。统一的异常处理定义清晰的业务异常体系并在全局如使用ControllerAdvice进行统一处理返回结构化的错误信息。日志记录关键路径在业务流程的关键节点如订单创建成功、库存扣减、支付回调记录 INFO 级别日志便于问题追踪。领域模型驱动尽可能将业务逻辑如订单的创建、状态流转封装在领域实体中让服务层变得“薄”而协调者角色清晰。代码即文档通过有意义的类名、方法名、变量名以及清晰的分层让代码自己解释自己。只有在必要且复杂的地方才添加注释。9. 总结回顾这次重构之旅我们并没有引入任何高深莫测的新框架或理论只是系统性地应用了那些经典但永不过时的设计原则单一职责、开闭原则、依赖倒置。我们将一团纠缠不清的“面条代码”梳理成了职责清晰、像乐高积木一样可以灵活组合的组件。重构的价值不在于让代码看起来更“高级”而在于让它更可靠、更易变。可靠的代码bug 更少线上问题更容易定位易变的代码当产品经理提出“增加一种新的优惠券类型”或“在创建订单后增加积分奖励”时你不再需要心惊胆战地修改核心流程而是可以自信地添加一个新的PriceCalculator实现或一个OrderCreatedEventListener。下次当你再面对那一坨让你皱眉的“坏代码”时希望你能想起这篇文章里的步骤先识别“坏味道”再编写测试保护网然后像外科手术一样一步步进行分解、封装和抽象。最终你会收获的不仅是一个更优雅的系统还有一份面对复杂代码时从容不迫的底气。