1. 代码重构的本质认知第一次听说代码重构是手艺活这个说法时我正在深夜加班修改一个祖传代码库。屏幕上那个长达800行的上帝类(God Class)正用20多个静态变量嘲笑着我的无能。当时我恍然大悟——重构不是简单的代码搬家而是像修复古董瓷器一样需要精准判断每一处裂纹的走向。1.1 重构与重写的区别红线新手最容易踩的坑就是混淆重构(Refactoring)和重写(Rewriting)。去年团队里有个热血新人看到旧系统就直接提议这代码没法要了我们重写吧结果项目延期三个月。两者的本质区别在于重构在不改变外部行为的前提下改善内部结构。就像给老房子做加固住户可以正常生活但建筑结构更合理了。Martin Fowler的《重构》中强调的小步快跑模式就是典型代表。重写推倒重建的颠覆式改造。适合当原有架构已无法满足核心需求时采用但风险极高。就像把四合院拆了盖摩天楼期间住户得全部搬离。我在金融系统改造中总结的三问法则现有代码是否还能通过修补满足核心需求重写后的测试覆盖率能保证不低于原系统吗是否有足够的缓冲期处理数据迁移只有当三个答案都是是时才考虑重写。1.2 手艺人的工具箱优秀重构者需要培养三种核心能力代码嗅觉(Code Smell)识别就像中医望闻问切要能快速定位问题。例如发散式变化(Shotgun Surgery)修改一处需要改动多个类依恋情结(Feature Envy)某个方法过度访问其他类的数据数据泥团(Data Clumps)总是同时出现的三四个数据项模式映射能力看到混乱代码时能想象出它应有的设计模式形态。比如发现多个条件分支处理相似逻辑 → 可能适用策略模式对象创建逻辑散落各处 → 考虑工厂模式回调地狱 → 观察者模式解耦安全重构技巧测试覆盖率不足时先用包装而非替换策略复杂方法先用提取方法拆解再考虑移动位置优先重构被频繁修改的代码区域根据Git历史分析2. 重构前的准备工作2.1 建立安全网去年重构一个电商促销系统时我差点酿成重大事故。当时自信满满直接修改核心计算逻辑结果上线后优惠券金额全乱套了。现在我的原则是没有安全网就别走钢丝。测试覆盖率要求关键路径必须100%覆盖边界条件至少3个测试用例异常流程要有明确断言对于遗留系统可以用测试缝(Test Seam)技巧// 原始代码 public BigDecimal calculateDiscount(Order order) { // 复杂计算逻辑... } // 改造后 public BigDecimal calculateDiscount(Order order) { return new DiscountCalculator(this).calculate(order); } // 测试专用子类 class TestableDiscountCalculator extends DiscountCalculator { Override protected BigDecimal complexLogic(Order order) { return TEST_VALUE; // 可控测试值 } }2.2 代码考古学接手陌生代码时我习惯先做三个分析变更热力图用git log --stat查看哪些文件频繁修改git log --since1 year ago --prettyformat: --name-only | sort | uniq -c | sort -rg | head -20依赖关系图使用JDepend或ArchUnit检查包依赖ArchTest static final ArchRule layer_dependencies layeredArchitecture() .layer(Controller).definedBy(..controller..) .layer(Service).definedBy(..service..) .layer(Persistence).definedBy(..dao..) .whereLayer(Controller).mayNotBeAccessedByAnyLayer() .whereLayer(Service).mayOnlyBeAccessedByLayers(Controller) .whereLayer(Persistence).mayOnlyBeAccessedByLayers(Service);运行时分析通过APM工具定位性能热点2.3 制定重构路线图好的重构计划应该像手术方案般精确。我的模板包含优先级矩阵结合修改频率和复杂度划分四个象限依赖顺序从底层独立模块开始逐步向上回滚方案每个阶段都要有快速回退点曾经在物流系统中我们按这个顺序推进独立的基础工具类领域模型纯逻辑部分与外部系统对接的防腐层业务流程编排层最终的用户界面层3. 经典重构模式实战3.1 卫语句取代嵌套噩梦遇到这种代码就像走进迷宫public void processOrder(Order order) { if (order ! null) { if (order.getItems() ! null) { for (Item item : order.getItems()) { if (item.isValid()) { // 真实逻辑... } } } } }用卫语句(Guard Clause)改造后public void processOrder(Order order) { if (order null || order.getItems() null) { return; } order.getItems().stream() .filter(Item::isValid) .forEach(this::processValidItem); }关键技巧将异常路径提前返回使用Stream API替代循环嵌套每个方法只处理阳光路径3.2 策略模式解耦业务逻辑支付系统中最常见的if-else链public void processPayment(Payment payment) { if (ALIPAY.equals(payment.getType())) { // 支付宝处理逻辑 } else if (WECHAT_PAY.equals(payment.getType())) { // 微信支付逻辑 } // 更多else if... }重构为策略模式后public interface PaymentStrategy { boolean support(String type); void execute(Payment payment); } // 上下文路由器 public class PaymentContext { private ListPaymentStrategy strategies; public void process(Payment payment) { strategies.stream() .filter(s - s.support(payment.getType())) .findFirst() .orElseThrow(...) .execute(payment); } }进阶技巧结合Spring的Conditional自动注册策略使用枚举维护支付类型与策略的映射为策略接口添加异步支持3.3 领域驱动设计的重构应用在重构订单系统时我通过领域驱动设计(DDD)识别出核心问题业务逻辑分散在20多个Service中订单状态变迁没有明确约束优惠计算与库存管理耦合重构步骤划定限界上下文订单核心域支付支撑子域物流通用子域建立聚合根public class Order { private OrderId id; private ListOrderItem items; private OrderStatus status; public void cancel() { if (status OrderStatus.DELIVERED) { throw new IllegalStateException(已发货订单不可取消); } this.status OrderStatus.CANCELLED; this.domainEvents.add(new OrderCancelledEvent(this.id)); } }引入领域事件解耦系统// 订单服务 public void cancelOrder(OrderId id) { Order order repository.findById(id); order.cancel(); repository.save(order); // 事件由基础设施层统一发布 } // 库存处理器 TransactionalEventListener public void handle(OrderCancelledEvent event) { inventoryService.releaseStock(event.getOrderId()); }4. 重构中的避坑指南4.1 测试陷阱我曾迷信测试覆盖率直到遇到这种情况// 原始代码 public boolean isAdult(int age) { return age 18; } // 测试用例 Test void testIsAdult() { assertTrue(service.isAdult(20)); }看起来覆盖率100%但重构时改为age 18后测试仍然通过。教训是要测试边界条件18、17、19验证业务规则而不仅是代码执行使用Property-based Testing工具如jqwik4.2 性能幻觉在一次微服务改造中我们高兴地将延迟从200ms降到50ms。但上线后发现99线飙升到800ms。原因是本地测试数据量不足没考虑分布式事务开销网络跳数增加的影响性能重构原则始终在生产环境做基准测试使用混沌工程验证退化场景监控关键指标P99、错误率、系统负载4.3 文档债务最危险的陷阱是代码干净了但文档没更新。我的解决方案将文档作为重构的DoD(Definition of Done)之一使用JavaDoc的implSpec标注设计意图架构决策记录(ADR)保存重大变更原因# ADR 2023-01: 支付策略模式改造 ## 现状 原有if-else结构已难以维护新增支付方式需要修改核心类 ## 决策 采用策略模式每种支付方式实现独立策略类 ## 后果 - ✅ 新增支付方式只需添加新类 - ⚠️ 需要策略注册机制 - ⚠️ 动态加载策略需要类路径管理5. 重构后的持续维护5.1 代码度量体系建立量化指标防止倒退圈复杂度单个方法不超过10重复度使用SonarQube检测3%依赖指数模块间依赖不超过合理范围测试缝保留关键测试缝供紧急情况使用5.2 演进式架构在CI流水线中加入架构守护tasks.register(archUnitTest, Test) { useJUnitPlatform { includeTags archTest } shouldRunAfter test }5.3 重构文化培育推动团队形成重构习惯每周代码诊所会议技术债看板可视化重构作为KPI考核项童子军规则离开时比来时更干净记得有次在代码评审时我发现新人提交的代码中有个明显可以优化的地方。不是直接指出问题而是问你觉得这个逻辑有没有可能用设计模式来改善结果他不仅当场提出策略模式的解决方案后来还主动发起对整个模块的重构。这就是手艺人的传承。
阅读完成 · 觉得有帮助?