阿杰在小区门口开了一家面馆。开业不到三个月,后厨就乱成一锅粥:三个厨师各有一份手抄的汤底配方,味道时咸时淡;菜单上有二十八道菜,后厨每天手忙脚乱;他还打算买一台烤箱,准备“将来也许有客人点西餐”。三个问题,恰好对应软件工程里流传最广的三条原则——DRY、KISS、YAGNI。配方写几份、菜单做几道、烤箱买不买,就是这三条原则要回答的问题。它们经常被拿来和 SOLID 五原则一起讨论,不是因为它们属于 SOLID 的正式成员,而是因为它们和 SOLID 一样,是程序员在真实项目里最常受益的“生存法则”。这篇文章,就用阿杰这家小面馆,把这三张便签彻底讲透。
掌握 DRY/KISS/YAGNI 的边界,能让你在 AI 项目的代码评审中一眼识别“知识重复”与“过度设计”——无论是特征口径还是训练管线,都能在简洁与可维护之间做出经得起时间考验的决策。
可视化演示
展示DRY消除知识重复与抽象提取、KISS最简方案选择对比与YAGNI臆想需求剪枝,切换模式掌握三大设计原则的核心权衡。
本节配有交互动效(进入视口后自动加载,约 3KB)。
DRY原则与知识重复消除 DRY要求每个知识在系统中唯一表示,重复代码是知识重复的症状而非根本问题 通过提取公共抽象消除重复,但过早抽象比重复更危险需谨慎判断时机 duplication = knowledge_repeated, fix via abstraction when pattern is stable DRY消除的是知识重复而非表面代码相似
DRY 核心要义
阿杰的汤底是招牌,可他发现三个厨师各自凭记忆默写配方——同一个“汤底知识”有了三个来源。有一次配方升级,三个人各改各的,第二天汤就变味了。这就是“知识重复”的代价:一份配方变成三份,等于把“改一次”变成“改三次”,还每次都改出分歧。
DRY 全称 Don't Repeat Yourself,核心思想是“系统中的每份知识都必须有单一、明确、权威的表示”。重点在“知识”——业务规则、约束、决策点——而不是代码文本。两段代码长相一样,但各自服务于不同业务规则,就不算 DRY 意义上的重复,强行合并反而制造灾祸。这就好比“员工餐的三分钟出锅”和“招牌面的三分钟出锅”,看着一样,标准完全不同。
消除重复的手段也远不止“抽函数”一种:常量、配置表、数据字典、代码生成器,都能让知识回到单一来源。惩罚则是线性的——每多一份副本,需求变更时就要多改一处、多冒一次“漏改”的风险。
故事的结局是:阿杰把三份手抄配方烧了,换成一张塑封的配方卡挂在汤锅上方。以后改配方只改这一张——这就是“单一事实来源”(Single Source of Truth)。
- 总维护成本 $C_{\text{总维护}}$一次业务规则变化,把所有副本都改到位所花费的总代价。在面馆里,就是“升级一次汤底配方”要通知几个厨师、各改几遍。
- 对 $n$ 份副本求和 $\sum_{i=1}^{n} c_i$第 $i$ 个副本的修改成本是 $c_i$,从第 1 份加到第 $n$ 份。每多一份配方手抄稿,就多一个求和项。
- 近似为 $n \times \bar{c}$当各副本成本接近时,总和约等于“份数 × 平均单份成本”。这是线性增长——份数翻倍,成本翻倍。
KISS 简约之道
阿杰想把菜单扩到二十八道菜,隔壁退休的大厨老周看了直摇头:“三道面、一锅高汤、三样浇头,够了。菜一多,备料、训练、出错的地方全跟着多。” KISS 说的就是这个道理:够用就好。
KISS 全称 Keep It Simple, Stupid,主张“用最简单的方案解决当前问题”。但要分清:简单不等于简陋。必要的错误处理、边界校验、安全措施一样不能少——删掉它们不叫简化,叫偷工减料。KISS 追求的是“任何一个人都能读懂,并且改起来不会心惊胆战”:可读性永远优先于炫技,一个三行直白的写法永远优于一行谁都看不懂的黑魔法。
复杂度的来源有两类:方案本身的难度,以及方案之间的配合成本。菜单从三道加到二十八道,不只是多了二十五道菜的做法,还多了“配菜—备料—传菜”之间数百个组合配合。后者才是压垮后厨的最后一根稻草。
阿杰最后把菜单印成了三行字,但每一碗面都做到位。简单不是少做,而是把力气花在对的地方。
- 系统总难度 $D_{\text{系统}}$一个方案整体有多难理解、多难修改、多难不出错。
- 各部分难度求和 $\sum_{i=1}^{m} d_i$$d_i$ 是第 $i$ 个组成部分自身的复杂程度,$m$ 个部分直接相加。三道面对应的 $m$,远小于二十八道菜。
- 交互难度 $\sum_{1\le i<j\le m} I_{ij}$任意两个部分($i<j$)之间的配合成本 $I_{ij}$ 都要计入。菜越多,后厨彼此配合的“暗号”越多,交互项急速膨胀。
YAGNI 需求边界
阿杰看隔壁西餐厅生意好,动了买烤箱的心思。老周问他:“小区里有人点西餐吗?” 阿杰愣住:“将来也许有。” 老周说:“那就等有人点的那天再买。烤箱占地方、要保养、要学用法,而'也许'可能永远不会来。”
YAGNI 全称 You Aren't Gonna Need It,中文常译作“你根本用不上它”。它反对的是为臆想中的需求提前实现功能。需求有四类:已确认的、已计划未开始的、可能出现的、纯属臆想的。YAGNI 针对的是后两类。提前实现一个需求,至少要付三笔账:实现成本、持续的维护成本、机会成本——做它的时间本来可以做别的事。
用数学的语言说,YAGNI 是一个期望值判断:把“需求真的出现”当成概率事件,用概率乘价值,再减去提前实现的成本。
阿杰最后没买烤箱,但在厨房墙上留了一个插座。“留白”和“提前建好”之间的差别,就是 YAGNI 的全部智慧——不建设施,但保留可能性。
- 期望净收益 $E$提前做这个功能,长期看是赚还是亏。负数就坚决不做。
- 概率 $P(\text{需求出现})$这个“将来的需求”真的会出现的概率。臆想需求通常远小于 2%——就像阿杰的“西餐梦”。
- 期望价值 $P \times V$就算需求真来了,提前写的代码往往要返工,价值还要再打折。$0.02 \times 10$ 万元,只值 2 千元。
- 减去成本 $C_{\text{提前实现}}$实现成本不是一锤子买卖,还要持续维护。$E = 0.2 - 3 = -2.8$ 万元,期望为负。
过度工程边界
阿杰嫌两位传菜小哥各记各的暗号,想搞一个“传菜管理台”统一管理。结果店里只有两个人、三句暗号,管理台反而每天要登记,成了新的负担。这是为了 DRY 而 DRY,制造了过度工程。反过来,招牌面的“三分钟计时法”贴在每口锅边,每个灶台各有一份——因为灶台离得远,统一计时器反而要喊话。这个场景里,复制是绝对正确的。
过度工程的头号来源是“错误抽象”:两段代码只是碰巧相似(假重复),被强行合并后,每一处都要加 if、加配置去弯折回各自的行为,抽象层变成了新的维护负担。判断一份重复值不值得抽象,可以套用下面的成本模型:复制方案的成本随副本数线性上涨;抽象方案要先付一次性建设成本,之后每处修改会便宜一些。
遇到疑似重复,先问三句:①它们是否源于同一个知识源?②它们是不是总在同步变化?③合并后是否要为特例开洞?三条都“是”才值得抽象,任何一条“否”,就保持复制。阿杰最后给传菜暗号挂了块小白板,给计时法多印了三张贴纸——该抄的抄,该留的留。
- 复制方案成本 $C_{\text{复制}}(n)$$n$ 份副本,每份修改成本 $c_0$,总成本直线上升。
- 抽象方案成本 $C_{\text{抽象}}(n)$先花 $C_{\text{建设}}$ 建抽象层,之后每处修改都便宜一些,单份只要 $c_1$(且 $c_1 < c_0$)。
- 差值 $\Delta G(n)$抽象相对复制省下的钱。$\Delta G > 0$ 抽象更划算;$\Delta G < 0$ 就老老实实复制。
重构实践案例
打烊盘点,阿杰发现库房有三张食材清单:写在菜单背面的、贴在冰箱上的、还有记账本里的。每次进货要核对三遍,还总对不上。他把三张合一,挂一张在库房门口——这是真重复,合并。但“员工餐”和“招牌面”的备菜步骤看着很像,他故意保留了两张流程卡——因为一个是给员工管饱、一个是给客人出品,标准不同,强行合并就要写一堆“如果是员工餐就……”。前者该合,后者该留。
重构时机怎么抓?先识别坏味道:逐字粘贴的代码、Ctrl+C/Ctrl+V 的历史、改一处漏一处的 bug 记录。再看重复的性质:如果两份代码即将由不同团队、不同节奏去演进,复制就是对的;如果第三处相同逻辑已经出现,说明知识边界已经清晰,这时候抽象一次成型率最高。请记住,重构不是大爆炸式的重写,而是“先统一入口,再小步验证,再逐步扩散”的渐进过程。
阿杰在库房门口挂了一张清单,在员工餐窗口贴了一张流程卡。该合的合,该留的留——这就是重构的真实模样:不是消灭所有重复,而是让每份知识都待在该待的地方。
- 重构净收益 $R_{\text{重构}}$这笔重构做下来,长期是赚还是赔。为重构而重构,是过度工程的另一种形态。
- 当前维护成本 $C_{\text{当前维护}}$三张食材清单,每周进货核对三遍,这就是持续的当前成本。
- 重构后成本与一次性投入合并后每周只核对一遍,但整理清单本身要花半天。$R > 0$ 才动手;渐进式重构能压低一次性投入。
三原则协同
阿杰把三张便签贴在收银台后面,老周来吃面,指着便签说:“这不是三条规矩,是一道流程:先想清楚要不要做——有没有人点;再想怎么做最简单——用几口锅;最后看哪里写了第二份配方——该收的收。”
一个需求面前,三个原则各管一个决策点,顺序不能乱:YAGNI 管“做不做”,KISS 管“怎么做得最简”,DRY 管“哪份知识漏了源、写了几份”。冲突也由此化解:为尚未确认的需求建立抽象层,DRY 会被 YAGNI 否决;强行合并两处语义不同的假重复,DRY 会被 KISS 否决。三个原则不是互相打架的三条规矩,而是同一套过滤网的三层目数。
阿杰把三张便签钉在一起,像一套过滤网:臆想需求被滤掉,复杂方案被滤掉,剩下的干净逻辑里,没有第二份配方。
- 第一道闸 YAGNI$E \geq 0$,需求真实存在且有正向期望收益,才放行。
- 第二道闸 KISS在放行的方案里,选系统总难度 $D$ 最小的那个。
- 第三道闸 DRY方案落地时,确保每份知识只有一个权威来源,没有冗余副本。
务实落地清单
阿杰每天打烊后围着后厨转一圈,嘴里念念有词:“烤箱动过吗?菜单还能更短吗?配方是不是又被人抄了一份?” 这三问,就是他的“打烊三查”。原则不落到动作上,就只是口号。
把三条原则翻译成行动清单,可以分为三个时机。设计期三问:这个抽象是为哪份知识服务的?现在有第二个使用方吗?明天需求变了,这里会是最脆弱的地方吗?评审期三查:重复的两处会不会朝相反方向变?抽象层有没有在弯折特例?有没有为臆想需求预留接口?提交前三问:这段代码删掉一半还能跑吗?我为哪个“未来”付了钱?这个函数名是不是比它的实现还难懂?
更近一步,把清单写进团队 Code Review 模板,并在 README 里维护一份“本仓库的单一知识源清单”。新成员第一天上班就知道配方在哪、有几张——三原则就从个人风格变成了团队共识。
三年后,阿杰的面馆菜单还是三道面,厨房还是那几口锅,汤底却一年比一年好——因为配方只有一张,而那张卡,他每天擦。
- 检查项 $Q_1$这个抽象或复制背后,有一个明确的“知识源”吗?——配方是不是只有一张。
- 检查项 $Q_2$还有比当前方案更简单的做法吗?——菜单还能不能更短。
- 检查项 $Q_3$我在为哪一个已经确认的需求付钱?——烤箱有没有人点。
- 逻辑与 $\wedge$三项全部为真才通过;只要有一个“否”,就暂停、简化、再回头。
AI 应用场景
特征口径统一治理(DRY)
阶段:特征工程与 MLOps
解决的问题:训练和推理两条管线各自实现了同一个“用户近 7 日活跃度”特征算式,上线后两边数值对不上,模型表现和离线评测严重偏差。传统后端重复代码最多出 bug,AI 项目里重复的特征口径则会直接毁掉模型的可靠性——因为模型学的是口径,口径有多份,模型就学了个四不像。
具体案例:某推荐系统团队把散落在 40 多个脚本里的 200 多个特征算式全部收敛到一个 Feature Store,训练和推理统一从这里取数,离线在线一致性从 82% 提升到 99.6%。
最简 Prompt 验证(KISS + YAGNI)
阶段:模型应用与提示词工程
解决的问题:新来的同学一上来就堆 500 字 prompt 模板,还引入多智能体框架,结果可读性差、调试困难,效果却不升反降。KISS 要求先用最简 prompt 跑通主路径,YAGNI 提醒不要为“未来可能的分支对话”提前搭建整套 agent 编排。
具体案例:某客服机器人把 200 行复杂 agent 编排砍成“一条 30 行 system prompt + 一个工具调用”,首轮解决率不降反升;等业务真的出现多分支需求时,再按“第三处出现”的原则逐步引入框架。
模型服务公共代码治理(DRY 与复制的边界)
阶段:模型服务化部署
解决的问题:三个模型服务各自复制了一份“文本清洗”代码,一处修了正则 bug,其余两处漏改,线上出现脏数据。但一开始就建公共 SDK 也不行——三个服务的清洗规则还在快速各自演化,过早抽象会天天改公共库。正确节奏是:先允许复制,等规则稳定、出现第三个使用者后再收敛。
具体案例:某 NLP 平台团队先用“复制 + 注释”让三个服务各自演进,半年后三个服务的清洗规则趋于一致,才抽出公共 SDK,并配上统一回归测试。
Python 演示
# -*- coding: utf-8 -*-# duplicate_detector.py: 重复代码侦探,结合 DRY / KISS / YAGNI 给出处理建议 def extract_lines(source): # 按行拆分并去掉空行,保留代码本体 return [line.rstrip() for line in source.splitlines() if line.strip()] def find_duplicate_blocks(lines, min_len=3): # 滑动窗口:内容完全相同的连续行序列,视为潜在知识重复 seen = {} dups = {} for i in range(len(lines) - min_len + 1): block = tuple(lines[i:i + min_len]) if block in seen: dups.setdefault(block, []).append(i) else: seen[block] = i return dups def suggest(block, positions): # 三原则联合决策: # 出现 >= 3 次且块较长 -> DRY 抽象 # 只有 2 处 -> 先复制观察,等第 3 处再抽象(KISS + YAGNI) if len(block) >= 3 and len(positions) >= 3: return "建议抽象成公共函数 (DRY 生效)" if len(positions) == 2: return "先复制,等第 3 处出现再抽象 (KISS + YAGNI)" return "暂时保持现状" def main(): sample = """def make_soup(): boil_water() add_bones() stew_three_hours() def make_noodle(): boil_water() add_bones() stew_three_hours() def make_broth(): boil_water() add_bones() stew_three_hours() def employee_meal(): boil_water() add_noodles() serve() def signature_dish(): boil_water() add_noodles() serve()""" lines = extract_lines(sample) dups = find_duplicate_blocks(lines) if not dups: print("no duplicate found, keep it simple.") return for block, positions in sorted(dups.items(), key=lambda kv: -len(kv[1])): print("block %d lines x %d places: %s" % (len(block), len(positions), suggest(block, positions))) if __name__ == "__main__": main()
def find_duplicate_blocks(lines, min_len=3):滑动窗口检测逐字相同的连续行序列,这是“知识重复”的第一个候选信号。block = tuple(lines[i:i + min_len])把连续多行打包成元组当字典键;元组可哈希,才能用字典统计出现次数。dups.setdefault(block, []).append(i)每遇到一次重复块就记下它的位置,用于统计“被复制了几份”。def suggest(block, positions):把 DRY/KISS/YAGNI 翻译成一条可执行的判断规则,是整个脚本的“决策脑”。if len(block) >= 3 and len(positions) >= 3:同一段逻辑出现三处以上,知识边界基本清晰,才建议抽象。if len(positions) == 2:只出现两处,先保持复制,等第三处出现再动手——这是 YAGNI 与 KISS 的联合答案。if __name__ == "__main__":只有直接运行时才执行主流程,被 import 时不产生副作用。
常见误区
误区我写代码时特别注意“不复制粘贴”,看到两行一样就赶紧抽函数,否则就觉得自己违反了 DRY。
正解DRY 针对的是知识——业务规则、约束、决策点——而不是文本。两段代码字面相同,但分别属于“堂食定价”和“外卖定价”,各有各的调整节奏,复制比合并更省。就像员工餐和招牌面能用同一锅水,配方却必须分开,因为服务对象和标准不同。干掉的是“知识的第二个来源”,不是“相同的字符”。
为何常见:“Don't Repeat Yourself“的名字太像”别复制粘贴”,让人误以为重复的单位是代码,而不是知识。
误区KISS 嘛,就是能跑就行,接口、分层、错误处理都不需要,越少代码越简单。
正解KISS 是“用最简单的方案满足需求”,不是“砍掉必要的设计”。必要的接口、错误处理、边界校验都不能砍,砍了叫简陋,不叫简单。三道面的菜单背后,汤底依然要熬三小时——方案可以简单,该有的工序一样不能少。
为何常见:简单与简陋的边界看起来模糊,初学者容易把“不写”当成“简化”,把必要的设计当成“复杂”。
误区YAGNI 让我别想将来,所以需求没来我就不留任何余地,将来再说。
正解YAGNI 反对“为臆想的需求提前实现功能”,但不反对“已计划需求的规划”,更不反对留出扩展的余地。不买烤箱,不等于不在墙上留插座。好的架构留扩展点,不建扩展设施——这两者的差别,正是 YAGNI 的分寸所在。
为何常见:把“不为将来做”极端化成“不想将来”,忽略了“留白”与“规划”之间的区别。
误区我建了一个公共类把两处相似逻辑统一了,我很 DRY。
正解为合并而合并的“错误抽象”恰恰是过度工程的头号来源。两段代码碰巧相似、但不共享同一知识源时,强行抽象后每一处都要加 if、加配置去弯折回自己的行为,抽象层本身变成新的维护负担——就像把所有餐具都抽象成“可握持容器”,筷子反而要硬塞进这个体系。抽象是手段,不是 DRY 本身。
为何常见:抽象是工程师最熟悉的“高级手段”,下意识觉得“用了抽象 = 专业 = DRY”,却忽略了知识源是否真的只有一个。
误区代码重复就谈 DRY,代码复杂就谈 KISS,需求没谱就谈 YAGNI,三个原则偶尔还会打架。
正解它们其实是一道决策流程的三道闸门:YAGNI 管“做不做”,KISS 管“怎么做”,DRY 管“做几份”。顺序是 YAGNI → KISS → DRY。先过滤臆想需求,再选最简方案,最后治理重复——在一个简单方案里把该收敛的知识收敛掉,才是完整解法。
为何常见:每条原则单独看都像一句口号,不了解分工就容易拿它们互相否定,而不是把它们串成一条流水线。
面试题
Q1. DRY 原则中“重复”主要指什么?
- A.代码文本的重复出现
- B.知识信息的重复来源← 正确答案
- C.函数调用的重复次数
- D.代码注释的重复描述
答案:B。DRY 针对的是“知识”——同一条业务规则如果有两个源头,以后改规则就要改两遍,还会改岔。就像面馆的两份手抄配方,哪怕排版不同,只要同源于“汤底秘方”就是知识重复;字面相同的两行反而不一定是。因此选 B,它是“知识的单一来源”而非“文本去重”。
Q2. 能用普通数组解决的任务,却先搭一个类继承体系,这主要违背哪条原则?
- A.DRY 原则
- B.YAGNI 原则
- C.KISS 原则← 正确答案
- D.依赖倒置原则
答案:C。KISS 要求用最简单的可行方案。普通数组够用时,引入类体系是纯额外成本——就像面馆为三道面搞三十道菜的后厨。虽然它也沾一点 YAGNI 的边,但题眼是“用复杂方案解决简单问题”,核心违背的是 Keep It Simple,因此选 C。
Q3. 下列哪些是 YAGNI 的正确理解?
- A.不实现臆想中的未来需求← 正确答案
- B.完全拒绝任何前瞻性思考
- C.等需求真正明确后再实现← 正确答案
- D.只做当下已确认的部分← 正确答案
答案:ACD。YAGNI 反对“为也许不会来的需求提前买单”,A、C、D 都是正解。B 是常见极端化——不买烤箱不等于不留插座,留出扩展点不算提前造设施,因此 B 错误。
Q4. 两段代码字面完全一样,一处属于“堂食菜单”、一处属于“员工餐”,未来定价逻辑各自独立变化。最佳做法是?
- A.立即抽取公共函数
- B.保持复制并加注释说明← 正确答案
- C.建立继承体系统一收敛
- D.删除一段复用另一段
答案:B。字面相同不代表知识重复。堂食和员工餐的“为什么这么定价”是两个知识源,合并后每次改动都要加分支,把简单变复杂。保持复制并注释说明,等真正出现第三个共同知识源再抽象——这正是 YAGNI 与 KISS 的联合答案,因此选 B。
Q5. 判断两段代码是否构成“知识重复”的关键是?
- A.代码行数是否接近
- B.变量命名是否相同
- C.是否源于同一条业务规则← 正确答案
- D.是否写在同一个文件
答案:C。知识(业务规则、约束、决策点)才是 DRY 的度量单位。行数、命名、文件位置都是表面特征——两份配方哪怕排版不同,同源于“汤底秘方”就是知识重复;员工餐和招牌面哪怕看起来像,源头不同就不是。因此选 C。
Q6. 以下哪些是典型的“过度工程”信号?
- A.大量从未被使用到的抽象层← 正确答案
- B.为臆想需求预留的配置开关← 正确答案
- C.抽象层频繁加分支适配特例← 正确答案
- D.用配置表维护公共业务规则
答案:ABC。A 是“为不存在的问题付钱”、B 是 YAGNI 明令禁止的提前设施、C 是错误抽象开始弯折特例的典型症状,三者都是过度工程。D 恰恰是 DRY 推荐的“单一事实来源”手段——把公共规则收敛进配置表,不是过度工程,因此不选。
Q7. 一份业务规则被复制成 n 份,每次需求变更需逐份修改,总维护成本与 n 的关系近似为?
- A.常数级
- B.对数级
- C.线性增长← 正确答案
- D.指数增长
答案:C。每份副本的修改成本相近,总和约等于 n × 单份成本,是线性增长——复制一份,成本翻一倍。这正是 DRY 的经济学依据:副本越多,“改一遍”就变成“改 n 遍”。C 正确,D 的指数级是危言耸听,但线性已足够可怕。
Q8. 面对一个需求,DRY / KISS / YAGNI 的正确决策顺序是?
- A.YAGNI → KISS → DRY← 正确答案
- B.DRY → KISS → YAGNI
- C.KISS → DRY → YAGNI
- D.YAGNI → DRY → KISS
答案:A。先用 YAGNI 问“这个需求真的存在吗”,过滤臆想;再用 KISS 问“最简单的方案是什么”;最后用 DRY 问“里面的知识有没有第二份来源”。顺序颠倒,就会为无需求做抽象,或把简单方案搞复杂。因此选 A。
Q9. 下列哪些是消除“知识重复”的正确手段?
- A.抽取公共函数统一入口← 正确答案
- B.把规则收敛进单一配置表← 正确答案
- C.强制合并所有相似代码
- D.用代码生成器从一份定义生成← 正确答案
答案:ABD。A、B、D 都让“知识”回到单一来源,只是载体不同;从一份定义生成多处代码并不算重复,因为源头只有一个。C 是“字面强迫症”,把语义不同的东西硬捏在一起,等于制造错误抽象,因此不选。
Q10. 某功能若提前实现,一次性成本 3 万元;若将来需求出现,预估价值 10 万元,且需求出现概率约为 2%。按 YAGNI 期望收益模型,应?
- A.不做,期望收益约 -2.8 万元← 正确答案
- B.必做,潜在价值有 10 万元
- C.不做,期望收益为 -1 万元
- D.先做,成本仅 3 万元
答案:A。E = P × V − C = 0.02 × 10 − 3 = −2.8(万元),期望净收益为负,提前实现是亏本买卖。B 只看到价值的分子,忽略了概率与成本;C 算错了算式;D 只看到成本,没算期望。臆想需求的真实概率常常连 2% 都不到——YAGNI 的数学内核就是期望值,因此选 A。