“以后好像会需要,要不要提前做出来?”
开发过程中,这种诱惑经常出现。提前预留配置选项,提前搭好多语言支持的结构,甚至提前建好数据库表,让管理员可以在后台修改。
今天要讲的 YAGNI,正是正面回答这种诱惑的“不要”。它和 KISS、DRY 一起被称为三大开发原则。
YAGNI 是“You Aren’t Gonna Need It”的缩写,意思是“反正你用不上它”。
这个说法源自极限编程(XP)这种开发方法。Kent Beck、Ron Jeffries 等 XP 阵营的人经常把它挂在嘴边,后来逐渐固化为一项原则。
核心主张只有一个。
功能要在真正需要时再实现,而不是预计可能需要时就提前实现。
提前做出来到底有什么问题?
你可能马上会反驳:“提前做出来,以后不是更方便吗?”问题在于,这种预测大多数时候都是错的。
Martin Fowler 在一篇总结这一主题的文章中,把提前创建功能的成本分成四类。
| 成本 | 内容 |
|---|---|
| 实现成本 | 为创建当前马上用不到的功能而花费的时间 |
| 延迟成本 | 本应利用这段时间创建的真正功能被推迟 |
| 维护成本 | 即使是不使用的代码,测试、重构和修复 bug 也会持续跟着来 |
| 修复成本 | 真正需要时,需求已经发生变化,因此必须推倒重做 |
最痛的是最后一项。“以后”真正到来时,那时的需求几乎总是和自己预想的样子不同。最后,连重做提前创建的代码的成本也要重复支付。
YAGNI 在实际工作中被打破的场景
口头上说起来大家都会点头,但在实际工作中,它会悄悄以这些形式出现。
1. 提前预留参数
// 现在只有邮件通知
func sendNotification(user: User, message: String, channel: String = "email",
retryCount: Int = 3, priority: String = "normal",
template: String? = nil) {
// channel时才运行的代码 "email"
}
所有调用方都只有 sendNotification(user: user, message: msg)。其他参数是为了“以后接入 Slack 通知时使用”而创建的,但 Slack 通知的需求始终没有出现。
2. 提前铺设协议
明明只有一个实现,却先创建协议的情况。创建一个 UserService时顺便创建UserServiceProtocol,但在第二个实现出现之前,这个协议只是增加文件数量的装饰品。
3. “抽成配置不是更好吗?”
因为害怕硬编码,就把所有值都移到配置文件和管理后台中。实际上,这些配置一年甚至改不了一次;配置越多,“修改这个值会影响哪里?”这类问题就越多。
哪些不属于 YAGNI
为了避免误解,先划清界限。YAGNI 不是以下意思。
首先,它不是叫你不要做设计。让代码易于修改的努力(好的命名、小函数和测试)现在就有价值,因此不属于 YAGNI。相反,代码应该易于修改,这样才能在“真正需要时”快速完成。
它也不是叫你把难以撤销的决定都推迟。数据库选型或 API 公开规范等一旦确定就很难更改的事项,本来就应该提前思考。YAGNI 针对的是“明明以后很容易添加,却提前实现的功能”。
我的判断标准
当我想“要不要提前做出来?”时,会问自己两个问题。
这个功能确实需要的依据在路线图上,还是只存在于我的想象中?
以后再添加,会不会比现在做贵得多?
如果依据只是想象,而且以后添加的成本差不多,我就不做。大多数情况都属于这一类。
总结
从 YAGNI 原则中可以记住三件事。
- 功能要在需要时再做,而不是觉得可能需要时就提前做。
- 提前创建的功能会带来四重成本:实现、延迟、维护和修复。
- 不过,良好的设计习惯和难以撤销的决定不属于 YAGNI。
把 KISS、DRY 和 YAGNI 放在一起看,三个原则最终可以归结为一句话:把今天需要的东西,以简单的方式只实现一份。
如果有哪怕一个功能是抱着“以后应该会用到”的想法做完后就闲置,也许 2 年后你会亲手把它删掉。

