共计 1754 个字符,预计需要花费 5 分钟才能阅读完成。
从上帝类到模块化:一个血泪案例
上周接手了一个订单管理系统,核心的 OrderService 类足足有 1200 行代码。当我尝试添加退款功能时,发现需要同时修改订单状态计算、支付流水更新和物流通知三个毫不相关的逻辑——这正是典型代码腐烂的症状。更可怕的是,这个类有 27 个公有方法,其中 15 个方法共享着同一个 Map<String, Object> 类型的参数。

SOLID 三剑客与 skill 内核
让我们聚焦 skill 最核心的三个原则:
-
SRP(单一职责):就像厨房里菜刀不应当同时用来开罐头,一个类应该只有一个引起它变化的原因。实测显示,违反 SRP 的类平均修改频率是合规类的 3.2 倍(数据来源:SonarQube 2023 报告)
-
OCP(开闭原则):好的代码应该像乐高积木,通过扩展而非修改来增加功能。在 Spring 生态中,这体现为对
@Conditional和策略模式的灵活运用 -
LSP(里氏替换):当你把
ArrayList替换成LinkedList时程序仍能正常工作,这就是 LSP 的理想状态。这条原则特别适用于微服务接口设计
Spring Boot 重构实战
第一步:拆解上帝类
原始代码结构:
// 反例:超级订单服务
@Service
public class OrderService {
// 包含了订单、支付、物流等混合逻辑
public void createOrder(OrderDTO dto) {/* 200 行代码 */}
public void cancelOrder(OrderDTO dto) {/* 300 行代码 */}
// 还有 15 个类似方法...
}
重构后的模块划分:
classDiagram
class OrderCoreService
class PaymentService
class LogisticsService
OrderFacade --> OrderCoreService
OrderFacade --> PaymentService
OrderFacade --> LogisticsService
第二步:接口隔离演进
初始接口:
public interface BigInterface {void processOrder();
void updatePayment();
void notifyLogistics();}
优化后的角色化接口:
// 符合 ISP 原则的拆分
public interface OrderProcessor {void process(Order order);
}
public interface PaymentOperator {void update(Payment payment);
}
测试护航重构
使用 JUnit5 的契约测试确保行为一致:
@ExtendWith(MockitoExtension.class)
class OrderServiceContractTest {
@Test
void shouldKeepOriginalBehaviorWhenRefactor() {
// 用相同的输入验证新旧实现输出一致
Order oldResult = oldService.createOrder(request);
Order newResult = newFacade.createOrder(request);
assertThat(newResult).usingRecursiveComparison()
.isEqualTo(oldResult);
}
}
性能与设计的平衡术
抽象成本实测
在 MacBook Pro M1 上测试(单位:纳秒 / 调用):
| 调用方式 | 直接调用 | 接口调用 | 动态代理 |
|---|---|---|---|
| 平均耗时 | 12 | 15 | 48 |
| 99% 线耗时 | 18 | 22 | 112 |
结论:合理的抽象层增加(<3 层)带来的性能损耗可忽略,但滥用动态代理会显著影响性能。
避免过度设计的红灯
- 当发现自己在为 ” 可能的需求 ” 写代码时
- 当你的接口只有一个实现类时
- 当单元测试需要 Mock 超过 3 个依赖时
记住 YAGNI(You Aren’t Gonna Need It)原则:今天需要的设计比明天可能需要的架构更有价值。
留给 DDD 的思考题
在领域驱动设计中,我们常常面临这样的选择:是把订单状态变更拆分成 OrderStatus 值对象(更符合 SRP),还是保持为 Order 实体的内部方法(更符合领域完整性)?你的选择是什么?为什么?
(提示:可以结合你项目的 QPS 要求、团队规模、领域复杂度来思考)
