50、代码坏味道:夸夸其谈未来性 - 实战移除不必要的抽象
说实话,我见过太多代码被“过度设计”毁掉的例子。今天聊的这个坏味道,叫“夸夸其谈未来性”。
什么意思呢?就是你在写代码时,总想着“万一将来要扩展呢”、“说不定以后会支持这个功能”。于是你提前加了一层抽象、一个接口、一个工厂类。结果呢?那个“将来”从来没来过。代码却变得又臭又硬。
我个人习惯是:只对当前需求做抽象,不为未来买单。你想想看,未来是不可预测的。你猜的需求,十有八九是错的。
什么是“夸夸其谈未来性”?
说白了,就是过度预测未来需求。你为“可能”会出现的场景,提前引入了不必要的复杂性。
举个例子。你只需要保存用户信息到数据库。结果你写了个 UserRepository 接口,又写了个 MysqlUserRepository 实现类。还加了个 UserCache 抽象层。为什么?因为“万一将来要换数据库呢”、“万一将来要加缓存呢”。
嗯,这里要注意:这种“万一”通常不会发生。就算发生了,到时候重构也来得及。你现在加的抽象,反而让代码更难理解、更难修改。
核心原则:YAGNI(You Aren't Gonna Need It)—— 你其实不需要它。
实战案例:一个过度抽象的订单系统
我在项目中遇到过这样一个案例。一个订单处理模块,只有两种订单:普通订单和会员订单。结果代码里却出现了这样的结构:
// 抽象层:订单处理器接口
public interface OrderProcessor {
void process(Order order);
}
// 抽象层:订单验证器接口
public interface OrderValidator {
boolean validate(Order order);
}
// 抽象层:订单通知器接口
public interface OrderNotifier {
void notify(Order order);
}
// 实现类:普通订单处理器
public class NormalOrderProcessor implements OrderProcessor {
private OrderValidator validator;
private OrderNotifier notifier;
public NormalOrderProcessor(OrderValidator validator, OrderNotifier notifier) {
this.validator = validator;
this.notifier = notifier;
}
@Override
public void process(Order order) {
if (validator.validate(order)) {
// 处理订单逻辑
notifier.notify(order);
}
}
}
// 实现类:会员订单处理器
public class VipOrderProcessor implements OrderProcessor {
// 几乎一样的代码,只是折扣逻辑不同
// ...
}
你发现问题了吗?三个接口,两个实现类,还有依赖注入。但实际业务逻辑只有十几行。这就是典型的“夸夸其谈未来性”。
我当时接手这个模块时,花了整整一天才理清这些抽象之间的关系。其实核心逻辑很简单:
// 重构后:一个类搞定
public class OrderService {
public void processOrder(Order order) {
// 1. 验证订单
if (!validate(order)) {
throw new IllegalArgumentException("订单无效");
}
// 2. 计算价格(普通订单和会员订单的区别就在这里)
BigDecimal price = calculatePrice(order);
// 3. 保存订单
saveOrder(order, price);
// 4. 发送通知
sendNotification(order);
}
private BigDecimal calculatePrice(Order order) {
if (order.isVip()) {
return order.getAmount().multiply(new BigDecimal("0.9")); // 会员9折
}
return order.getAmount();
}
private boolean validate(Order order) {
return order != null && order.getAmount() != null
&& order.getAmount().compareTo(BigDecimal.ZERO) > 0;
}
private void saveOrder(Order order, BigDecimal price) {
// 保存到数据库
}
private void sendNotification(Order order) {
// 发送邮件或短信
}
}
你看,去掉了所有不必要的接口和抽象,代码反而更清晰了。将来如果要支持第三种订单类型,直接在 calculatePrice 里加个分支就行。没必要提前搞什么策略模式。
如何识别“夸夸其谈未来性”?
我总结了几条判断标准:
| 症状 | 典型表现 | 严重程度 |
|---|---|---|
| 接口只有一个实现类 | 写了接口,但项目中只有这一个实现 | ⚠️ 中等 |
| 抽象层没有实际调用者 | 工厂类、策略类写好了,但没人用 | 🔴 严重 |
| 为“将来”预留的扩展点 | 代码注释里写着“预留扩展” | 🟡 轻微 |
| 过度使用设计模式 | 一个简单功能用了3个以上设计模式 | 🔴 严重 |
避坑指南:我曾经在一个项目中,看到有人为“将来可能支持多种支付方式”提前写了一个支付策略工厂。结果项目上线两年,始终只有微信支付一种方式。那个工厂类成了无人敢动的“僵尸代码”。每次修改支付逻辑,都要绕过那层抽象,直接改底层实现。你说这抽象有什么意义?
重构步骤:移除不必要的抽象
如果你发现代码里有这种坏味道,可以按以下步骤重构:
- 找出所有只有一个实现类的接口。这些接口大概率是多余的。
- 检查调用方。如果调用方只依赖接口,且只有一个实现,直接把接口和实现合并。
- 删除工厂类和配置类。如果工厂类只生产一种对象,直接 new 出来就行。
- 内联方法。把那些只有一行调用的方法,直接内联到调用处。
- 运行测试。确保重构后功能不变。
举个例子。假设你有这样的代码:
// 重构前
public interface PaymentService {
void pay(BigDecimal amount);
}
public class WechatPaymentService implements PaymentService {
@Override
public void pay(BigDecimal amount) {
// 微信支付逻辑
System.out.println("微信支付:" + amount);
}
}
// 调用方
public class OrderController {
private PaymentService paymentService;
public OrderController() {
this.paymentService = new WechatPaymentService();
}
public void checkout(BigDecimal amount) {
paymentService.pay(amount);
}
}
重构后:
// 重构后:去掉接口,直接使用实现类
public class WechatPaymentService {
public void pay(BigDecimal amount) {
// 微信支付逻辑
System.out.println("微信支付:" + amount);
}
}
public class OrderController {
private WechatPaymentService paymentService;
public OrderController() {
this.paymentService = new WechatPaymentService();
}
public void checkout(BigDecimal amount) {
paymentService.pay(amount);
}
}
你看,代码少了三分之一,可读性却提高了。将来如果真的需要支持支付宝,再提取接口也不迟。到那时,你对支付逻辑的理解会更深入,提取的接口也会更合理。
什么时候可以保留抽象?
当然,我不是说所有抽象都是坏的。以下情况可以保留:
- 当前就有多个实现。比如你同时支持 MySQL 和 Redis 两种存储。
- 框架强制要求。比如 Spring 的接口注入。
- 测试需要。你需要 mock 接口来写单元测试。
- 团队约定。如果团队有统一的架构规范,可以遵守。
但记住:抽象是有成本的。每多一层抽象,就多一层理解成本、维护成本和调试成本。不要为了“优雅”而抽象,要为“解决实际问题”而抽象。
我的建议:写代码时,先写最直接的实现。等确实出现了重复代码或扩展需求,再考虑提取抽象。这叫“三次原则”——同一个逻辑出现三次,才考虑抽象。两次以内,复制粘贴都比抽象好。
核心知识体系
下面这张图总结了本章的核心逻辑:
总结一下:不要为未来写代码,要为现在写代码。未来是不可预测的,你猜的需求大概率是错的。等需求真的来了,再重构也不迟。到那时,你对业务的理解更深入,做出的抽象也更合理。
记住:好的代码不是“考虑周全”的代码,而是“容易修改”的代码。去掉不必要的抽象,就是让代码更容易修改的第一步。