本节摘要:简洁性要求代码以最低的本质复杂度实现功能,两条指导箴言是 KISS(保持简单)与 YAGNI(只做当下需要的)。本节剖析过度设计与过早优化的典型症状与代价,给出 DRY 原则的正确使用边界(不当的抽象比重复更糟),并澄清"简洁不等于行数少"的常见误解。
阅读完本节,你应当能够:
一个五人团队的内容管理系统,上线前架构师说:"万一以后要换数据库,我们先把数据访问抽象出来。"于是加了连接器接口、两个实现类、一套配置装配,所有查询都过抽象层。三年后,第二数据库从未到来,而每个新人都得先学这套只有一个真实实现的间接层;每次修数据相关的缺陷,都要先在抽象层里把调用链走一遍。为永远不会发生的需求付出的复杂度,是纯粹的成本——它不提供任何保护,只提供税收。
这不是孤例。过度设计的变体到处都是:只有一个实现的接口、"以防万一"的配置项(从来没人配置过)、三层封装包着一个标准库函数、为想象中的"插件化"预留的扩展点。它们的共同点是把可能性当成了需求。YAGNI 原则对此给出冷峻的回答:你不会需要它(You Ain't Gonna Need It)——等到真需要的那天再写,那时你对需求的理解远比今天准确;而今天预建的抽象,大概率建在错误猜测上,真需求来临时反而是障碍。
另一路复杂度来自性能焦虑:"这个查询循环里我多写几层缓存吧"——没有任何测量表明这里是瓶颈。过早优化的两层代价:一层是当下浪费的工时与损失的清晰度;另一层更隐蔽,优化后的代码更难读也更难改,等真正的性能问题出现时,它反而是重构的阻力。
KISS(Keep It Simple, Stupid)要求始终选择满足需求的最简单实现。它落在四个操作面:一次只做一件事(函数与类的单一职责,第 3.3 节);按需引入抽象(第二个月才有第二个实现时,再提取接口——届时你既知道共性在哪,也确认了共性真的存在);渐进式构建(从能跑的最小版本起步迭代,而不是一次浇筑完所有功能);用最直白的表达(别人十秒能懂的普通循环,好过要推演三分钟的高阶技巧组合)。
YAGNI 是对"面向未来编程"的纠偏。它的判断句式是一个反问:这个功能、这层抽象、这个配置项,是现在的需求单上有的吗?不是,就不写。注意 YAGNI 不反对扩展性设计——用清晰的接口与单一职责保持"未来好改",这与预建"未来专用"的机制是两回事。前者保证灵活,后者预支猜测。区分的关键:灵活是结构上的留白,过度设计是代码上的预建。
每个问题自带一份"本质复杂度"——由业务规则本身的错综决定,无法消减。好的实现复杂度紧贴这条线;差的实现在线之上堆出"偶然复杂度"(自找的)。自查三问:这个概念是业务里真实存在的,还是我为代码方便发明的?删掉这层间接,功能会坏吗?新人需要学几个"本项目自创概念"才能干活?自创概念越多、越厚,偶然复杂度越大。
DRY(Don't Repeat Yourself)要求消除重复,但执行中有个著名的陷阱:两段代码恰好相似不等于本质相同。"用户名验证"与"收件人姓名验证"今天看起来一样,明天业务要求用户名支持中文而收件人不支持——当初强行合并的那个函数,现在要加参数、加分支,两个调用点又开始各传各的旗子,复杂度反而超过当初的两段重复。DRY 的正确触发条件是第三次出现且变化方向一致(三次法则):前两次相似可能是巧合,第三次的重复才是共性的证据。抽象要建在证据上,不建在预感上。
代码库里有种"负资产":没人调用但不敢删的函数、被注释掉的旧代码(第 3.1 节)、注释掉的死分支、"以后可能用"的工具类。它们占据阅读带宽、干扰搜索、暗示"这里可能有用"的疑虑。无用代码直接删除——版本库记得一切,真需要时找得回来。定期做"死代码清扫",让代码库里的每一行都是活着的。
很多"手写复杂度"是在重新发明轮子:手写循环过滤映射(现代语言一行流式表达)、手写字符串拼接解析(标准库现成函数)、手写单例工厂(语言内置机制)。标准库的实现经过高度优化与充分测试,用它不仅省代码,还把复杂度转移给了语言本身——读者看到的是意图,不是手法。
⚠️ 常见坑:把简洁性理解成行数竞赛,用密集的高阶特性把十行压成三行。复杂度的度量是读者理解成本,不是字符数——嵌套三层的高阶链式调用比十行朴素循环难懂得多。真正的简洁是"看起来就这么多事,一行不多"。
💡 关键直觉:每层抽象都有租金(理解成本、调用链跳转、命名负担)。只在租金低于重复成本时才值得租——而重复的成本,多数时候比直觉里的低。
| 复杂度来源 | 症状 | 处方 |
|---|---|---|
| 面向未来编程 | 单实现接口 备用配置项 | YAGNI 等需求真来 |
| 性能焦虑 | 无测量依据的优化 | 先测量后优化 |
| 错误的 DRY | 硬合并的恰好相似 | 三次法则再抽象 |
| 轮子工厂 | 手写标准库已有功能 | 用语言与标准库 |
| 不敢删 | 死代码与注释代码 | 直接删除 版本库兜底 |
下一节把简洁原则放进两种编程范式:面向对象与函数式各自的纪律清单。