53、代码坏味道:中间人 - 实战移除中间人
说实话,中间人这个坏味道,我见过太多次了。
它不像那些一眼就能看出来的大问题,比如几百行的方法、混乱的继承树。中间人很隐蔽,它披着「封装」的外衣,让你觉得「嗯,这样挺合理的」。但时间一长,你就会发现,代码里全是这种「传话筒」——它们不干活,只负责把请求转给真正的对象。
今天我们就来彻底撕掉这层伪装。
什么是中间人?
说白了,就是一个类,它的大部分方法都在调用另一个类的方法。它自己不处理任何业务逻辑,纯粹是「A 找 B,B 再找 C」里的那个 B。
举个例子,你想想看:
public class OrderService {
private OrderRepository repository;
public Order findById(Long id) {
return repository.findById(id);
}
public List<Order> findByUserId(Long userId) {
return repository.findByUserId(userId);
}
public void save(Order order) {
repository.save(order);
}
}
这个 OrderService 做了什么?什么都没做。它就是把 OrderRepository 的方法又包了一层。调用方本来可以直接用 repository.findById(),现在非要绕一圈经过 service。
我在项目中遇到过类似的情况,一个 Controller 层调 Service,Service 调 DAO,DAO 调 JPA Repository。每一层都在做「透传」,没有任何业务逻辑。结果就是,改一个查询条件,要改三层代码。你说累不累?
中间人是怎么产生的?
我总结了一下,主要有三个来源:
- 过度设计:一开始就想着「我要分层」、「我要解耦」,结果每层都变成了传话筒。
- 渐进式腐化:原本这个类是有逻辑的,后来逻辑被抽走了,但委托方法留了下来。
- 对封装的误解:觉得「不能让调用方直接访问 Repository」,于是硬加了一层 Service。
嗯,这里要注意第二种情况。我记得有一次重构一个订单模块,发现一个 OrderFacade 类,里面十几个方法全是委托。我查了 Git 历史,发现半年前它确实有逻辑,但后来业务调整,逻辑被移到了别处,委托方法却没人清理。这就是典型的「烂代码沉淀」。
如何识别中间人?
我个人习惯用两个方法:
- 方法计数法:打开一个类,数一下它自己的方法有几个,委托方法有几个。如果委托方法占大头,报警。
- 调用链追踪:看调用方是怎么用的。如果调用方每次都要经过三层才能拿到数据,那中间层大概率是多余的。
你还可以用 IDE 的「查找用法」功能,看看这个类被调用的地方。如果调用方都在做同样的事——先调 A,A 再调 B——那为什么不直接调 B 呢?
实战:移除中间人
我们来看一个真实的例子。假设有一个报表系统,代码结构是这样的:
// 中间人类
public class ReportService {
private ReportDataProvider dataProvider;
public ReportData getDailyReport(Date date) {
return dataProvider.fetchDaily(date);
}
public ReportData getWeeklyReport(Date start, Date end) {
return dataProvider.fetchWeekly(start, end);
}
public ReportData getMonthlyReport(int year, int month) {
return dataProvider.fetchMonthly(year, month);
}
}
// 调用方
public class ReportController {
private ReportService reportService;
public void showDailyReport(Date date) {
ReportData data = reportService.getDailyReport(date);
// 渲染报表...
}
}
你看,ReportService 什么都没干,就是转手。调用方 ReportController 完全可以绕过它,直接使用 ReportDataProvider。
重构步骤很简单:
- 内联方法:把
ReportService的每个方法,内联到调用方。 - 删除中间类:确认没有其他地方使用
ReportService后,删除它。 - 调整依赖:让
ReportController直接依赖ReportDataProvider。
重构后:
// 调用方直接使用数据提供者
public class ReportController {
private ReportDataProvider dataProvider;
public void showDailyReport(Date date) {
ReportData data = dataProvider.fetchDaily(date);
// 渲染报表...
}
}
代码从三层变成了两层,调用链短了,理解成本也降低了。
什么时候不该移除中间人?
任何重构手法都有例外。我建议你考虑以下情况:
| 场景 | 说明 |
|---|---|
| 接口稳定性要求高 | 如果中间层是公开 API,移除会导致所有调用方修改,那就不动。 |
| 未来确定会加逻辑 | 比如下个迭代就要在中间层加缓存、加权限校验,那可以保留。 |
| 跨进程/跨服务调用 | 如果中间层是 RPC 客户端或消息发送者,它本身就有存在的价值。 |
我曾经在一个支付项目中,保留了一个看似「中间人」的 PaymentGateway 类。虽然它当时只是委托给第三方 SDK,但我知道下个月要接入第二个支付渠道。如果当时把它内联掉,后面加渠道时就要改所有调用方。嗯,这种「有计划的保留」是可以接受的。
核心逻辑图
下面这张图展示了中间人的识别与移除流程:
避坑指南
最后,分享几个我踩过的坑:
- 不要一次性全删:如果中间人有多个调用方,先内联一个方法,跑通测试,再逐步推进。我曾经一次性删掉整个类,结果 CI 挂了半小时。
- 注意测试覆盖:移除中间人后,测试可能也需要调整。如果中间人的测试只是「验证调用了委托方法」,那这些测试可以直接删掉。
- 警惕「伪中间人」:有些类虽然方法看起来是委托,但内部做了参数校验、日志、缓存。这种不算中间人,别误删。
好了,关于中间人的内容就这些。记住一句话:代码里的传话筒,能少一个是一个。