布局 — 让修改只发生在一个地方
这一章讲四件事: 布局的唯一目标(效应局部化)和它的直接判据(变动率); 把东西收拢成模块的三层手段(封装、信息隐藏、关注点分离); 接口怎么把 「会变的部分」关进胶囊(OCP);以及架构层的最后保险(可逆性)。 主走查是原书自带的例子:每年都要改的税法,两种放法,一次修改各自动几处。
1. 目标:效应局部化
第 03 章把重复收敛到了一处。但「一处」放在哪、和什么放在一起,决定了下一次修改要读多少代码。 这就是本章的主题——布局。作者把它的目标写成一条原则:效应局部化(Local consequences): 把修改带来的影响,控制在局部1。
「效应」指修改的影响。效应不局部化是什么样:改一处,另一处完全不相干的地方跟着坏; 更糟的是你不知道哪里会坏——先得花时间排查影响范围,排查本身可能比修改还贵2。 效应局部化之后,要读的代码和受影响的面都缩在一个小圈子里。 作者还给了它的副产品:程序员只需理解当前阶段涉及的代码,不必一次掌握全部3。
实现它的总手法是模块化——模块这个概念第 02 章已引入(数据与函数的打包单位), 布局要回答的正是:哪些东西该打包进同一个模块。
效应局部化的判据(作者给的信号):
两个模块相互频繁调用
→ 说明「原本应该放在一起的要素,被分别放在了不同的模块中」[^4]
→ 该合并的合并,该新建的新建
图说:调用关系是布局错误的探测器。频繁互相调用=一条更紧的关系被切断了。
2. 三个思想:布局背后的价值观
作者借 Kent Beck 的《实现模式》立了三个思想,作为所有布局决定的价值坐标: 交流(第 02 章讲过)、简洁(第 03 章讲过)、灵活性(代码易于修改)4。 前两个已各自成章;灵活性在这一章展开,它有一个必须 先立的警告:
灵活性是双刃剑。 代码易于修改是好事,但「灵活」经常被拿来当复杂设计的借口—— 多余的灵活性是无用的,为它服务的代码最终「成为一堆带来多余复杂性的无用代码」5。 作者的药方:不从「设计得灵活」出发,而从简洁出发、用测试兜着,自下而上地获得灵活性6。
三个思想太抽象,落不到代码上。作者给了六个「桥梁」原则: 效应局部化(§1)、重复最少化(第 03 章)、逻辑与数据的一体化、对称性、声明式表达、变动率7。 本章展开其中四个;「对称性」与「声明式表达」相对次要,先给一句话版:
- 对称性:相同的思路,在代码的任何地方都以相同的形式出现——有「添加」就该有「删除」, 同组函数用同款参数(调用时传入的输入值)。它的深层作用是消除重复的准备工作:先把相似的东西摆对称, 完全相同的部分才看得出来、才收得拢8;
- 声明式表达:代码有两种说法,命令式描述解法(一步步怎么算), 声明式描述问题本身(什么算对)。声明式没有流程跳转,读起来就是一列事实9。
3. 变动率:按修改时间分组
这一节回答:「什么和什么放在一起」的可操作判据——不看功能相似,看修改时间相同。
变动率(Rate of Change)原则一句话:同时修改的元素,放在同一个地方; 在不同时间点修改的元素,放在不同的地方10。 作者点破它的本质:这是把对称性原则应用到时间上——同样的东西同等对待, 「东西」换成「修改的时间点」11。
为什么按时间分,不按功能分?因为布局服务的是下一次修改:一次修改动到的代码越集中, 效应越局部。作者给了一个判断框架:一个模块里混着多个「修改理由」, 它就脆弱——修改机会多、每次修改都要动它、改动还容易波及无关部分12。