Java 遗留代码重构:从何入手以及如何不破坏项目
每位 Java 开发人员迟早都会遇到遗留代码。这可能是十年前编写的单体应用程序,或者是使用过时模式、缺乏测试和包含"魔法数字"的项目。重构此类代码并非易事,但对于维持系统的生命力来说绝对必要。
在本文中,我们将分析重构 Java 遗留代码的主要策略,展示具体示例,并提供实用建议,帮助您在不冒破坏生产环境风险的情况下改进代码。
为什么遗留代码需要重构?
遗留代码不仅仅是"旧代码"。它是这样的代码:
- 难以维护和扩展
- 没有足够的测试覆盖率
- 包含重复和"代码坏味道"(code smells)
- 使用过时的库和方法
重构可以改善代码的可读性、性能和可测试性。然而,主要规则是——不改变系统的外部行为。每个更改都必须是安全且可控的。
Java 遗留代码的重构策略
1. 创建由测试组成的保护网
在触碰代码之前,先编写测试。如果代码难以测试,这可能很困难。使用 Characterization Tests(基于当前行为的测试)方法。
示例:您有一个处理订单的方法:
public class OrderProcessor { public double calculateTotal(Order order) { double total = 0; for (Item item : order.getItems()) { if (item.getPrice() > 100) { total += item.getPrice() * 0.9; // 10% 折扣 } else { total += item.getPrice(); } } if (order.isPremium()) { total *= 0.95; // 额外 5% 折扣 } return total; }}编写一个记录当前行为的测试:
@Testpublic void testCalculateTotal_WithPremiumAndExpensiveItems() { Order order = new Order(); order.addItem(new Item("Laptop", 1000)); order.addItem(new Item("Mouse", 50)); order.setPremium(true); double expected = (1000 * 0.9 + 50) * 0.95; // 902.5 assertEquals(expected, new OrderProcessor().calculateTotal(order), 0.001);}现在您有了一个"保护网"。可以放心重构,而不必担心改变逻辑。
2. 拆分大方法(Extract Method)
遗留代码最常见的问题之一是长达数百行的"怪物"方法。将它们拆分成小的、命名良好的方法。
重构前:
public void processInvoice(Invoice invoice) { // 50 行验证代码 if (invoice.getCustomer() == null) throw new IllegalArgumentException(); if (invoice.getItems().isEmpty()) throw new IllegalStateException(); // ... 还有 20 行验证代码 // 100 行折扣和税费计算代码 // 80 行发送电子邮件和保存到数据库的代码}重构后:
public void processInvoice(Invoice invoice) { validateInvoice(invoice); double total = calculateTotal(invoice); saveInvoice(invoice, total); sendConfirmationEmail(invoice);}
private void validateInvoice(Invoice invoice) { if (invoice.getCustomer() == null) throw new IllegalArgumentException(); if (invoice.getItems().isEmpty()) throw new IllegalStateException();}
private double calculateTotal(Invoice invoice) { // 计算逻辑 return invoice.getItems().stream() .mapToDouble(Item::getPrice) .sum();}
private void saveInvoice(Invoice invoice, double total) { // 保存}
private void sendConfirma