跳到主要内容

模块的松紧 — 内聚、耦合与正交

这一章讲三件事: 内聚的七个等级——怎么判断一个模块「纯不纯」; 耦合的六个等级——全局变量为什么是万恶之源;正交性怎么把两把尺推广到整个系统。 主走查:一个全局变量引发的三模块连环事故,按书里的原始情节走。

1. 两把尺:模块内拉紧,模块间放松

第 04 章把代码分成了模块。这一章回答下一个问题:分得好不好,怎么判断?

软件工程给了两把互补的尺1:

  • 内聚度:量一个模块内部的纯粹程度——里面的元素是不是在为同一件事服务。越高越好;
  • 耦合度:量模块之间关系的紧密程度——一个模块要不要懂另一个模块的内幕。越低越好。

为什么这两条合起来就等于「好」?作者给的论证是独立性:模块越独立, 复杂度越可控、越可靠、越好维护。而独立性=「模块内元素的关联性达到最强, 模块与模块之间的关联性减到最弱」——内聚管前半句,耦合管后半句2

内聚塌了的典型症状,书里列了四个,句句能对上日常体感3: 代码难理解、难维护、难复用、一改就碎

2. 内聚七级:模块的纯度光谱

这一节把内聚度从口号变成刻度。 原书按由劣到优列了七级,每级给一个识别要点4;其中第 4 级行话叫控制耦合,第 5 级叫特征耦合,第 6 级叫数据耦合:

等级名字模块里装的是什么识别要点
1巧合强度恰巧重复过的几段命令说不出这个模块是干嘛的,没法命名
2逻辑强度「某一类操作」的抽象集合一个入口,靠参数选择具体功能
3时间强度「同一时刻要做」的杂事初始化模块:清表、建空间……只因同时发生而聚在一起
4流程强度某个流程的一段流程图截一段打包;只用其中一个功能时没法用
5通信强度操作同一份数据的几个功能功能之间靠数据传递相连
6信息强度同一份数据结构的一组操作多个入口,每个入口一个固定功能
7功能强度一件事的全部命令「MatrixAdd(x, y)」:整个模块=一个动作

走读这张表,会发现分级标准其实只有一条:模块内的东西为什么聚在一起。 「恰巧在同一个文件里」(1 级)、「恰巧同时发生」(3 级)、「恰巧操作同一数据」(5、6 级)、 「为了同一件事」(7 级)——理由越接近「为了同一件事」,修改越安全。 功能强度模块之所以最优,书里的论证是:所有命令服务于一件事, 「仅修改一部分命令的可能性非常小」——要改,通常是所有使用者都遇到了同一个问题, 改了大家都受益5

两个细节值得记:

  • 逻辑强度有个诱惑:把所有输入输出、所有编辑操作收进一个「大而全」模块,看似整齐。 它的真实风险在接口:几种功能共用一个入口、靠参数区分,「参数的操作方面容易出现编程错误」; 好处也真实:相关功能集中,数据变更时只改这一个模块6。优劣并存,所以要会认;
  • 信息强度是实用主义的赢家。书里明说:不必强求 7 级, 6 级(同一数据结构的操作全在一个模块)「也有很高的实用价值」; 迫不得已做低内聚模块时,替代方案的讨论不许省7

3. 耦合六级:从共享内脏到只递纸条

这一节把耦合度变成刻度,并走全书最著名的事故例:一个全局变量的连环祸。

耦合的分级标准同样只有一条:模块之间靠什么传递影响。由劣到优六级8:

等级名字传递什么一句话判词
1内容耦合直接引用对方内部、共享命令摸了内脏
2公共耦合共享全局变量(不属于任何模块、谁都能改的公共数据)共用一块没锁的白板
3外部耦合共享对外声明的数据白板上有名有姓的区域
4控制耦合传「指令」指挥对方做什么打电话教对方怎么干活
5特征耦合传一整个数据结构,只用其中一角整箱寄送,只用一件
6数据耦合只传几个简单值(标量:单个的数或一小段文字)递纸条,看完即弃

主走查:全局变量 X 的事故链(书里原例)

公共耦合的害处,原书给了一个精确的三模块情节,原样走一遍9:

布局:模块 A、B、C 都使用公共区里的数据 X(一条用户记录)

第 1 步 模块 A 的需求变了:X 的长度要从 10 位改成 12 位
第 2 步 改 A 的程序员动手。他看得见 A 自己的代码;
X 不出现在 A 的任何接口(参数表)上,他无从得知
「还有谁在读 X」——书里的原话:公共数据
「不会出现在模块间的接口上,这使得代码不容易被解读」[^10]
第 3 步 A 改完,自测通过。模块 C 也在读 X,而 C 的程序员
与 A 的程序员互相不知情
第 4 步 C 在某个预想不到的地方炸了。排查开始:
先查 C 自己,再查 B,最后才发现源头在 A 的一次「无关」修改

图说:每一环都是理性决策;事故是结构制造的,不是粗心制造的。

这个链条里没有坏人:改 A 的人尽了责,是结构让他看不见 C。 这就是全局变量的本质问题——它把「谁依赖我」的信息藏了起来, 依赖关系不再出现在任何接口上,只能靠人脑记住,而人脑会忘10

公共耦合的辩护与反驳

书里诚实记录了反方观点:公共耦合也有省参数的好处—— 不用把数据一个个写进参数表,「现实中确实有不少采用公共耦合的事例」11。 作者的回应分两层12:

  1. 需要大量参数传递,本身就是模块设计不当的症状——「通过重新设计模块,再次审查数据的位置,参数的数量大多能够减少」;
  2. 参数多一眼就能看见(代价显性),全局变量的问题会潜伏(代价隐性)。显性的笨拙好过隐性的危险

控制耦合与特征耦合:两种「差点就好」

第 4 级控制耦合值得单独看,因为它常见且貌似无害:调用方传一个标志值 (比如「A=添加,D=删除」)指挥被调模块干哪件事。 书里给的双重害处13:调用方必须懂对方的内部逻辑才能正确传值(对方不再是黑箱); 而被调方拿到指令后要自己分岔,内聚度直接退化到逻辑强度(第 2 节那个「参数选择功能」的形态)。 第 5 级特征耦合是它的孪生:传一整个数据结构过去、只使用其中一小部分—— 接收方拿到的信息多到用不上,该用哪个、不该用哪个又要靠约定14。 书里补了一句松绑:如果接收方确实会处理结构里的全部数据,可以视为数据耦合15

耦合的三个补充看点

等级之外,书里给了三个容易漏看的角度16:

  • 混合耦合:一个返回值在不同情形下含义不同——正常时返回税率,出错时返回负数。 使用方必须记住这个暗号,漏记就出事。第 07 章的契约式设计会用更严格的方式取代这种暗号;
  • 个数:与越多模块耦合,风险按条数累加;
  • 方向:双向依赖的模块,害处等同高耦合——A 用 B、B 又用 A,两个都拆不出来。

尺度尽头:幂等与安全

耦合的最终目的是让调用方敢于把对方当黑箱。书里把这种「敢」拆成两条性质17:

  • 幂等性:同一操作执行多少次,结果都一样(书中例:abs(abs(-3)) 还是 3);
  • 安全性(此处指无副作用):调用不改变任何外部状态。

一个幂等且安全的服务,调用方失败了就重发,完全不用问「刚才那次算不算数」。 书里的现成例子是 HTTP 的 GET 请求(网页取内容的协议):设计上就要求幂等且安全, 所以网络出错时浏览器可以放心重试18。到这里,「低耦合」不再是审美—— 它决定了系统在不可靠环境里能不能简单正确地工作。

4. 正交性:把两把尺推广到整个系统

这一节回答:模块之外,系统层面怎么表达「松紧」。

正交性借自几何:直角坐标系里,x 轴上的移动不改变 y 值——两条轴互不干扰。 代码的正交性:修改其中一组代码,不会影响另一组19。 书里的例子:访问数据库的代码与界面代码正交,于是换界面不用动数据层,换数据库不用动界面20

正交的收益两条21:修改局部化→开发与测试周期缩短、可复用; 出问题的部分可以被隔离——正交的系统坏一块修一块,不塌方。

实现正交的主力是层次化:把系统分成若干层,每层只使用下一层提供的功能22

这套做法在原书的设计原理清单里也有名字——层次原理:结构上讲究层次, 同种处理不跨越多个层次——比如打开文件与关闭文件这类成对的操作, 申请与归还要发生在同一层次23

书里用网络协议举例(协议=通信双方约定的报文格式与规则)24: 不懂底层的以太网(一种常见的组网线路)怎么跑,照样能理解文件传输服务怎么搭;底层从以太网换成别家, 上层一行不改——层次是放大版的数据耦合:每层只跟相邻层做有限、明确的交易。

层次化也有书里点名的两条坏处25:

  • 连锁修改:加一个字段(表里的一列数据),从界面到数据库每一层都要加一遍——层次越多,穿越越苦;
  • 性能损耗:数据每穿一层要转换一次格式,层数多本身就有开销。

后一条有个更普适的名字——间接化: 在两个模块之间插入「媒介模块」,让它们互不直接接触。书里引了那句著名的格言: 「计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。」 间接化保住了低耦合与可维护性,代价是让处理变冗长—— 这是一笔明码标价的交易:每加一层,买一份独立,付一份性能26

反方向的条件句同样锋利:使用全局数据会最大化耦合——共享数据的所有成分被焊在一起, 正交性从结构上不可能27。这条把第 3 节的事故链升格成了定理。

5. 作者的判断与证据

书里给了证据的:

  • 七级内聚、六级耦合的分级出自 1970 年代结构化设计运动(书引 Glenford Myers《Composite/Structured Design》1978 与日文工程教材),是本书里历史最长的思想遗产;
  • 幂等/安全与 HTTP GET 的联系有具体协议依据,可回溯到 HTTP 规范。

作者的推测与展开,要分开看的:

  • 「需要大量参数=设计不当」是作者的强判断,方向对但书里没有展开论证;
  • 等级间的边界案例(如「接收方用满整个结构就算数据耦合」)是作者给出的调和,非原始分级的一部分。

6. 边界与局限

  • 刻度是教学工具,不是自动打分机。 实际模块常同时呈现多级特征;书里的用法是「识别最差的成分,优先消除」,不是精确评级。
  • 低内聚有时是刚需。 时间强度模块(初始化)天然低内聚,但「所有初始化集中一处」本身有工程价值;书里也承认「有时也需要实现其他强度的模块」,只要替代方案的讨论不缺席7
  • 耦合等级偏局部。 它量的是两个模块之间的一次关系;现代系统里还有跨服务、跨网络的耦合形态,书写作时(2016,更早的教材源)未覆盖。
  • 层次化的坏处在微服务时代被放大:书里的「连锁修改」一条,在今天按业务边界重新划层后缓解,但这属于本书之后的进展,书内无此讨论。

7. 可带走的

  1. 自查一个模块的内聚:试着用一句话说出它「是干嘛的」——说不出,多半是巧合强度;
  2. 看聚合理由,不看摆放位置:「同时发生」「在同一文件里」都不是聚合的好理由,「为同一件事」才是;
  3. 全局变量的问题不是风格,是信息:它把「谁依赖我」从接口上抹掉了——事故链因此查不到源头;
  4. 参数多=设计在喊疼:先重审数据的归属,再考虑共享;
  5. 别用标志值指挥模块做事(控制耦合)——把「要做的事」拆成各自的函数,而不是传暗号;
  6. 传结构只取一角,是伪优雅:要么只传用得上的几个值,要么真用满;
  7. 写「失败可重试」的函数:幂等+无副作用,调用方才敢把你当黑箱;
  8. 系统的松紧靠层次:每层只跟相邻层交易;同时记住层次的代价——字段一加穿全层。

8. 原文地图

主题原书章原文位置
内聚度定义、七级清单4.1 内聚度text/71-ch04.txt:15(搜「纯粹程度」) · text/71-ch04.txt:16(搜「七个等级」)
巧合强度4.1 内聚度text/71-ch04.txt:22(搜「巧合强度」) · text/71-ch04.txt:190(搜「重复使用」)
逻辑强度与参数错误4.1 内聚度text/71-ch04.txt:56(搜「逻辑强度」) · text/71-ch04.txt:65(搜「编程错误」)
时间强度(初始化)4.1 内聚度text/71-ch04.txt:83(搜「时间强度」) · text/71-ch04.txt:86(搜「初始处理模块」)
信息强度多个入口4.1 内聚度text/71-ch04.txt:135(搜「信息强度」) · text/71-ch04.txt:151(搜「接入点」)
功能强度最优4.1 内聚度text/71-ch04.txt:157(搜「功能强度」) · text/71-ch04.txt:171(搜「限制在该模块内部」)
低内聚症状4.1 内聚度text/71-ch04.txt:186(搜「难以理解」)
信息强度实用价值4.1 内聚度text/71-ch04.txt:208(搜「实用价值」) · text/71-ch04.txt:211(搜「省掉讨论代替」)
耦合六级清单4.2 耦合度text/71-ch04.txt:225(搜「耦合度测量」) · text/71-ch04.txt:227(搜「六个等级」)
内容耦合4.2 耦合度text/71-ch04.txt:233(搜「内容耦合」)
公共耦合三害4.2 耦合度text/71-ch04.txt:248(搜「公共耦合」) · text/71-ch04.txt:252(搜「模块间的接口上」)
公共数据 X 事故4.2 耦合度text/71-ch04.txt:267(搜「模块 C」)
公共耦合辩护与反驳4.2 耦合度text/71-ch04.txt:271(搜「短处」) · text/71-ch04.txt:279(搜「重新设计模块」)
控制耦合4.2 耦合度text/71-ch04.txt:296(搜「控制耦合」) · text/71-ch04.txt:55(搜「逻辑强度」)
特征耦合与松绑4.2 耦合度text/71-ch04.txt:312(搜「特征耦合」) · text/71-ch04.txt:347(搜「视为数据耦合」)
数据耦合黑箱4.2 耦合度text/71-ch04.txt:327(搜「数据耦合」) · text/71-ch04.txt:306(搜「黑箱」)
混合耦合4.2 耦合度text/71-ch04.txt:381(搜「混合耦合」)
个数与方向4.2 耦合度text/71-ch04.txt:392(搜「个数」)
幂等与安全、HTTP GET4.2 耦合度text/71-ch04.txt:401(搜「幂等性」) · text/71-ch04.txt:425(搜「GET」)
正交性定义4.3 正交性text/71-ch04.txt:446(搜「正交在几何学」) · text/71-ch04.txt:451(搜「正交的代码」)
数据库与 UI 正交的例子4.3 正交性text/71-ch04.txt:453(搜「访问数据库」)
层次化与网络协议4.3 正交性text/71-ch04.txt:471(搜「层次化」) · text/71-ch04.txt:494(搜「以太网」)
层次原理(层次化的原则名)3.33 层次原理text/39-ch03-33.txt:11(搜「不跨越多个层次」) · text/39-ch03-33.txt:12(搜「释放资源的处理」)
层次化的坏处4.3 正交性text/71-ch04.txt:513(搜「弱点」) · text/71-ch04.txt:514(搜「连锁效应」)
间接化格言与效率权衡3.25 效率性text/31-ch03-25.txt:44(搜「中间层」) · text/31-ch03-25.txt:38(搜「平衡」)
全局数据最大化耦合4.3 正交性text/71-ch04.txt:481(搜「全局数据」)

Footnotes

  1. 出处:「4.1 内聚度」第 15 段(text/71-ch04.txt:15,搜「纯粹程度」)与「4.2 耦合度」第 225 段(text/71-ch04.txt:225,搜「耦合度测量」)。

  2. 出处:「4.1 内聚度」第 179 段(text/71-ch04.txt:179,搜「关联性达到最强」)。

  3. 出处:「4.1 内聚度」第 186 段(text/71-ch04.txt:186,搜「难以理解」)。

  4. 出处:「4.1 内聚度」第 21 段(text/71-ch04.txt:21,搜「巧合强度」)、第 55 段(text/71-ch04.txt:55,搜「逻辑强度」)、第 82 段(text/71-ch04.txt:82,搜「时间强度」)、第 99 段(text/71-ch04.txt:99,搜「流程强度」)、第 120 段(text/71-ch04.txt:120,搜「通信强度」)、第 134 段(text/71-ch04.txt:134,搜「信息强度」)、第 156 段(text/71-ch04.txt:156,搜「功能强度」)。

  5. 出处:「4.1 内聚度」第 171 段(text/71-ch04.txt:171,搜「限制在该模块内部」)。

  6. 出处:「4.1 内聚度」第 65 段(text/71-ch04.txt:65,搜「编程错误」)与第 66 段(text/71-ch04.txt:66,搜「好的一面」)。

  7. 出处:「4.1 内聚度」第 208 段(text/71-ch04.txt:208,搜「实用价值」)与第 211 段(text/71-ch04.txt:211,搜「省掉讨论代替」)。 2

  8. 出处:「4.2 耦合度」第 232 段(text/71-ch04.txt:232,搜「内容耦合」)、第 247 段(text/71-ch04.txt:247,搜「公共耦合」)、第 282 段(text/71-ch04.txt:282,搜「外部耦合」)、第 295 段(text/71-ch04.txt:295,搜「控制耦合」)、第 311 段(text/71-ch04.txt:311,搜「特征耦合」)、第 326 段(text/71-ch04.txt:326,搜「数据耦合」)。

  9. 出处:「4.2 耦合度」第 267 段(text/71-ch04.txt:267,搜「模块 C」)。

  10. 出处:「4.2 耦合度」第 255 段(text/71-ch04.txt:255,搜「供毫无关系的模块使用」)。

  11. 出处:「4.2 耦合度」第 275 段(text/71-ch04.txt:275,搜「不用指定参数」)。

  12. 出处:「4.2 耦合度」第 279 段(text/71-ch04.txt:279,搜「重新设计模块」)。

  13. 出处:「4.2 耦合度」第 306 段(text/71-ch04.txt:306,搜「黑箱」)与第 55 段(text/71-ch04.txt:55,搜「逻辑强度」)。

  14. 出处:「4.2 耦合度」第 323 段(text/71-ch04.txt:323,搜「并不是都能用到的」)。

  15. 出处:「4.2 耦合度」第 347 段(text/71-ch04.txt:347,搜「视为数据耦合」)。

  16. 出处:「混合耦合」第 381 段(text/71-ch04.txt:381,搜「混合耦合」)与「耦合的个数与方向」第 392 段(text/71-ch04.txt:392,搜「个数」)。

  17. 出处:「幂等性与安全性」第 401 段(text/71-ch04.txt:401,搜「幂等性」)。

  18. 出处:「幂等性与安全性」第 425 段(text/71-ch04.txt:425,搜「GET」)。

  19. 出处:「4.3 正交性」第 451 段(text/71-ch04.txt:451,搜「正交的代码」)。

  20. 出处:「4.3 正交性」第 453 段(text/71-ch04.txt:453,搜「访问数据库」)。

  21. 出处:「4.3 正交性」第 460 段(text/71-ch04.txt:460,搜「提高生产效率」)与第 466 段(text/71-ch04.txt:466,搜「降低风险」)。

  22. 出处:「4.3 正交性」第 471 段(text/71-ch04.txt:471,搜「层次化」)。

  23. 出处:「3.33 层次原理」第 8 段(text/39-ch03-33.txt:8,搜「讲究层次」)、第 11 段(text/39-ch03-33.txt:11,搜「不跨越多个层次」)与第 12 段(text/39-ch03-33.txt:12,搜「释放资源的处理」)。它是原书「七个设计原理」之一。

  24. 出处:「层次化的好处与坏处」第 494 段(text/71-ch04.txt:494,搜「以太网」)。

  25. 出处:「层次化的好处与坏处」第 513 段(text/71-ch04.txt:513,搜「弱点」)与第 517 段(text/71-ch04.txt:517,搜「性能可能会变差」)。

  26. 出处:「3.25 效率性」扩展第 44 段(text/31-ch03-25.txt:44,搜「中间层」)与第 38 段(text/31-ch03-25.txt:38,搜「平衡」)。

  27. 出处:「4.3 正交性」第 481 段(text/71-ch04.txt:481,搜「全局数据」)。