讲解
学了全套木工机械之后,最难得的手艺是知道「这颗钉子用锤子敲两下就好,不必开台锯」。教程的最后一章专门泼冷水:设计模式解决的是真实的、反复出现的复杂度;当复杂度不存在时,模式本身就是复杂度。学界管那些「看起来在解决问题、实际在制造问题」的惯常做法叫反模式(anti-pattern)。
过度设计(over-engineering)是头号反模式,症状包括:给只有一种实现、且可预见的将来也只有一种实现的逻辑建接口;给三步流程套策略 + 工厂 + 建造者全家桶;为「万一以后要换数据库」预留抽象层,然后这个「以后」十年没来。每一层抽象都有真实成本:读代码的人多跳一次、改需求的人多改一处、新人上手慢一天。YAGNI(You Aren't Gonna Need It)原则说的就是这个:只实现今天需要的,把扩展性留给真正出现第二个需求时的重构。
单例滥用是另一个经典。因为「单例」名字听起来正经,很多人把全局变量都包成单例来获取心理安慰——但全局状态的耦合、测试污染一点没少。判断标准:这个对象唯一是「语义要求」(全系统就该一份,比如配置)还是「图省事」(懒得传参)?后者请老老实实走构造函数注入。
「为模式而模式」还有个隐蔽形态:简历驱动开发——想用某个模式(或某个框架)是因为简历上写着好看,而不是问题需要。抵御它的办法是团队评审时坚持一个仪式:写出「不用模式的最直白实现」和「用模式的实现」两段代码对比,能讲清楚模式版本省了什么、未来什么变化会让它值回票价,再合并。
那什么时候真的该用模式?三个信号里出现两个就值得认真考虑:同样的 if-else 分派出现了第三次(规则三);需求历史上真的在这个维度变过(比如折扣规则去年改了四次);多人协作、需要一套公共词汇描述这块结构。都不满足时,直白代码就是最好的设计——简单不是没有设计,而是设计成熟后的样子。
示例
同一条运费规则的两个实现:过度设计版(接口 + 工厂 + 服务,22 行仪式代码)对比直白版(一个纯函数),断言二者行为完全一致:
import assert from 'node:assert/strict';
// === 过度设计版:规则就一条,却建了一整套「可扩展」框架 ===
interface ShippingStrategy {
fee(weight: number): number;
}
class StandardShipping implements ShippingStrategy {
fee(weight: number): number {
return weight <= 1 ? 8 : 8 + (weight - 1) * 3;
}
}
interface ShippingFactory {
create(): ShippingStrategy;
}
class StandardShippingFactory implements ShippingFactory {
create(): ShippingStrategy {
return new StandardShipping();
}
}
class ShippingService {
constructor(private readonly factory: ShippingFactory) {}
quote(weight: number): number {
return this.factory.create().fee(weight);
}
}
// === 直白版:一个纯函数 ===
function shippingFee(weight: number): number {
return weight <= 1 ? 8 : 8 + (weight - 1) * 3;
}
const overEngineered = new ShippingService(new StandardShippingFactory()).quote(2.5);
const straightforward = shippingFee(2.5);
assert.equal(overEngineered, straightforward);
assert.equal(straightforward, 12.5);
assert.equal(shippingFee(0.5), 8);
console.log('两种写法结果一致:' + straightforward + ' 元 —— 规则不增长时,简单的就是对的');
数一下成本:过度设计版要理解 5 个类型、跳转 4 次才能看到真正的计算公式;直白版一目了然。注意结论不是「接口工厂服务都是垃圾」——当第二种运费规则(冷链、海外)真的出现时,把纯函数重构成策略 + 工厂是十分钟的事,而且到那时重构有了真实的驱动。提前抽象赌的是「变化一定会来」,而软件业的数据对这类赌注很不利。
常见坑
- 把「简单」当「没水平」:评审时给直白代码挑刺「为什么不用模式」,是在鼓励复杂度;评审该问的是「这层抽象挡住了什么真实的变化」。
- 重构时机错判:第二个需求出现时是重构的最佳点(变化真实存在、旧代码还没扩散);拖到第五个需求再抽象,成本翻倍。
- 反模式清单当背锅清单:「书上说过度设计不好」不能成为拒绝一切抽象的棍子;判断依据永远是「这个变化维度历史上真的在变吗」。
- 忽视团队熟悉度:团队没人读过访问者模式,引入它就等于埋下维护地雷;模式的选择受团队词汇表约束。
- 以为反模式只在代码里:流程层面的「委员会设计」「抄大厂架构」同样是反模式,且代价更大。
小结
反模式的内核是「复杂度没有对应的收益」:过度设计、单例滥用、为模式而模式。用规则三、变化历史、团队词汇表三个信号决定何时上模式;拿不准时,直白代码就是最好的答案。这是本教程的最后一章——模式是地图,不是目的地,祝你在真实代码里走得从容。