软件设计

YAGNI 总结:开发者认为“以后应该会用到”为什么代价高昂

“以后好像会需要,要不要提前做出来?”

3 分钟阅读
YAGNI 总结:开发者认为“以后应该会用到”为什么代价高昂 封面图

“以后好像会需要,要不要提前做出来?”

开发过程中,这种诱惑经常出现。提前预留配置选项,提前搭好多语言支持的结构,甚至提前建好数据库表,让管理员可以在后台修改。

今天要讲的 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 年后你会亲手把它删掉。