跳到主要内容

代码异味与语言反模式 — 让读者大脑过载的两类「没写错」

这一章讲三件事: 「结构不理想」的代码(异味)怎么按认知机制分门别类;「名字与实际不符」的代码(语言反模式)为什么更隐蔽也更耗脑; 以及认知负荷本身是怎么被测量的——包括往头上戴发光的头带。 它把第 04 章的「外部认知负荷」从个人视角翻到代码侧:写代码的人怎么少塞负担。

1. 全景:两种框架管两个层面

一段「没写错但难读」的代码
├─ 结构层面:代码异味 ── 方法太长、类太大、代码重复……
│ 来源:Fowler《重构》,业界共识
│ 认知挂钩:各对应一种「给哪套装置加压」
└─ 命名层面:语言反模式 ── 名字说的和做的不一致
来源:Arnaoudova 提出,较新
认知挂钩:误导长时记忆检索,制造分块错误
图说:异味是「长得别扭」,反模式是「长得骗人」。后者更毒,
因为读者不知道自己被骗。

2. 代码异味:按「压哪套装置」重新分组

代码异味是福勒(Fowler)在《重构》里提出的概念:结构不理想的代码,共 22 种,小到单个方法、大到整个代码库1。异味不一定意味着有 bug,但有异味的代码更容易出 bug——后文有数据。这里不复述 22 种,而是按书里的思路把它们按认知机制重新分组——每种异味之所以难读,是因为压到了不同的装置2:

**压工作记忆的:过长的参数列表、复杂的 switch。**工作记忆一次加工 2~6 个元素,参数超过 6 个,大脑就存不齐——读方法体时参数含义已经丢了3。但有一个缓冲:参数能不能两两打包取决于读者——line(xOrigin, yOrigin, xDestination, yDestination) 四个参数,懂坐标的人看成「原点一对、终点一对」两个组块;不懂的人就是四个散件4。异味清单是通用的,负担是按读者的存货逐人结算的。

**压组块的:过大的类、过长的方法。**方法名和类名本是给代码打包的钩子——见 square(5) 知平方,见 minimum() 知求最小;可一个几千行的「上帝类」里,可用的钩子太少,读者被迫退回逐行硬啃——正好回到第 02 章新手默写代码时的状态5

**制造分块错误的:重复代码(克隆)。**这个最阴险。库里有个 foo(),又出现一个长得几乎一样的 goo():读 goo() 时,长时记忆把 foo() 的存货调出来垫底——大脑顺手得出「goo 就是 foo」。**可它们不完全一样。**这个「goo 就是 foo」的假组块一旦形成,就是一条如假包换的迷思概念(第 08 章三条件:不正确、反复出现、坚信不疑),要吃很多次亏才会被推翻6

异味的实际代价有数据:Khomh 分析 Eclipse(一个著名的开源 Java 开发环境)的多个版本——上帝类在所有版本里都是易错性的重要因素;在超过 75% 的发行版中,带「过大的类」「过长的方法」异味的类更容易被改动7。异味不只难读,还预示着这片代码会反复出事、反复被改。

3. 语言反模式:名字在说谎

第二类问题结构上可能挑不出毛病,坏在名字。Arnaoudova 把它命名为语言反模式:代码的语言元素(名字、注释、签名)与它实际扮演的角色不符8。三个真实样本:

  • 名叫 initial_element,读者以为存着元素,实际存的是元素的下标;
  • 名叫 isValid,读者以为是布尔值(真/假),实际返回整数;
  • 名叫 getCustomers(取客户们),实际返回的是单个布尔值

这种事有多普遍?Arnaoudova 分析 7 个开源项目:11% 的 setter(设置属性值的方法)在设置之外还返回了值;2.5% 的方法,名字加注释描述的和实际干的南辕北辙;以 is 开头的标识符里,返回值不是布尔的占64%9。她配套做了检测工具(LAPD),可以作为 Eclipse 插件跑在 Java 项目里10

4. 主走查:Fakhoury 的四组实验——坏名字比坏格式更耗脑

「反模式加负认知负荷」这条因果链,是拿脑成像验的。2018 年,法库里(Fakhoury,Arnaoudova 的学生)找了 15 位被试读带植入错误的代码片段找错——找错是幌子,真测的是理解代码时的大脑消耗11

实验把代码片段调了四种口味,每组被试按不同顺序读,排除练习效应12:

组① 有语言反模式(名字骗人) → 读时盯得最久(眼动数据)
组② 有结构反模式(结构别扭) → 表现平平
组③ 又骗人又别扭 → ——
组④ 两者都没有(对照组) → 基线

戴着功能性近红外光谱头带(下节详述)测脑血氧:
组① 读起来,平均含氧血红蛋白显著上升 = 认知负荷实打实增加
组② 相对对照组:没有统计证据表明负荷增加

图说:意外在右下角——被试主观上最烦坏格式
(有人原话「糟糕的格式会显著增加阅读者的负担」),
但仪器显示真正烧脑的是坏名字。

走查每一步都有出处:四组设计与 15 人见 Fakhoury 实验设置11,眼动结果与「结构反模式无统计证据」见原文13。这条结论对工程实践是反直觉的:**团队里吵得最凶的格式问题(括号放哪、缩进几个空格),对大脑的负担其实小于一个撒谎的名字。**code style 自动化可以全交给工具,起名省下的力气一分都不能省。

反模式为什么骗得过大脑?两个机制,全是老朋友:一是负迁移给错误印象——读到 retrieveElements()(取「们」,复数),长时记忆调出「列表」的存货,你以为能对它排序、切片,实际它只返回单个元素14;二是分块抄近路——isValid 这个名字太像布尔,大脑懒得再验证,假组块就此焊死15

5. 插叙:认知负荷怎么测

上面实验里「脑血氧上升 = 负荷增加」这步凭什么成立?顺便把测量手段的谱系过一遍16:

手段原理短板
帕斯量表让人自评「刚才有多费劲」,9 级单题问卷,「极高」和「特别高」分不清,但便宜好用,仍是主力
眼动任务越难眨眼越少;瞳孔越大越难需要设备;个体差异
fMRI测脑部血流量,精度高人必须躺平不动,没法测真实编程
脑电图头皮电极测电压变化信号粗
功能性近红外光谱(fNIRS)头带发近红外光,测脑部供氧——血氧升=负荷升对动作敏感,但比 fMRI 自由得多

中川团队 2014 年用 fNIRS 头带做了个直接实验:让 10 位被试读同一算法的两个版本——一个正常写,一个故意改复杂(不改变功能,只让变量值频繁而无规律地翻新):10 人中 8 人读复杂版时脑血氧显著上升17。脑血氧和「代码难读」之间的因果链,就是这么一环环焊上的。注意各手段的共识:它们互相之间有相关性,帕斯量表的自评和生理测量结论大体一致——所以日常没有头带可用时,问一句「这段你觉得多费劲」并不比仪器差多少。

6. 作者的判断与证据

**有实验撑着的:**上帝类易错、75% 发行版易变(Khomh,Eclipse 数据);反模式流行度 11%/2.5%/64%(Arnaoudova,7 个项目);四组实验与脑血氧(Fakhoury,15 人,fNIRS+眼动);复杂版代码血氧上升(中川,10 人中 8 人)。

作者的发挥,要分开看:「按认知机制给 22 种异味重新分组」是作者对 Fowler 清单的再诠释——福勒本人当年并没有从认知角度出发,书里明确承认这一点18;「烟感报警器」式的用法(上一章的命名同框)这里再次适用:统计相关,不担保个案。

**书的坦白:**fNIRS 技术对负荷与语言的关系研究还在进行中;帕斯量表的单题形式学界一直有诟病。

7. 边界与局限

  • Fakhoury 实验 15 人、读的是植入错误的小片段——「坏名字比坏格式耗脑」的方向可信,幅度别引用;
  • 22 种异味清单写于代码形态不同的年代;「过长的方法」在今天多长算过长,清单没说也不该由它说;
  • 异味/反模式框架都面向「读的人」;有些反模式(如 isValid 返回整数)同时也是 API(给别的程序调用的一组入口)设计问题,本章只处理认知面;
  • 语言反模式的研究对象是 Java;动态语言里名字说谎的方式不同(第 13 章体检里的「易错性」一项会碰这个)。

8. 可带走的

  1. 评审代码时用三问分流:哪里压工作记忆(参数列表)?哪里没钩子(过大类)?哪里会造假组块(克隆)?
  2. goo() 复制自 foo() 时,要么参数化合并,要么让名字差异大到不至于认错;
  3. 名字与返回类型不符是最毒的反模式——is 开头就返回布尔,get 复数就返回列表;
  4. 团队为格式吵架的精力,建议挪一半去查名字与实际不符;
  5. 格式问题交给自动格式化工具,一劳永逸;
  6. 评估「这段代码难不难读」,问人自评(帕斯式)就够了,不必迷信生理指标;
  7. 上帝类不是一天建成的——每次「顺手再加个方法」都在给它添砖。

9. 原文地图

主题原书章原文位置
福勒与 22 种异味9.1 为什么存在异味的代码会加重认知负荷text/55-ch09-01-9-1.txt:5(搜「Martin Fowler」) · text/55-ch09-01-9-1.txt:13(搜「22种代码异味」)
上帝类与冗赘类9.1 为什么存在异味的代码会加重认知负荷text/55-ch09-01-9-1.txt:25(搜「上帝类」) · text/55-ch09-01-9-1.txt:27(搜「冗赘类」)
Khomh 易错性与易变性9.1 为什么存在异味的代码会加重认知负荷text/55-ch09-01-9-1.txt:37(搜「易错性」) · text/55-ch09-01-9-1.txt:39(搜「75%」)
参数列表压工作记忆9.1 为什么存在异味的代码会加重认知负荷text/55-ch09-01-9-1.txt:55(搜「超过6个」) · text/55-ch09-01-9-1.txt:63(搜「4个组块」)
过大类阻碍分块9.1 为什么存在异味的代码会加重认知负荷text/55-ch09-01-9-1.txt:67(搜「见名知意」)
克隆与假组块9.1 为什么存在异味的代码会加重认知负荷text/55-ch09-01-9-1.txt:75(搜「goo()」) · text/55-ch09-01-9-1.txt:79(搜「西西里防御的几种变例」)
语言反模式定义与样本9.2 低质量标识符对认知负荷的影响text/56-ch09-02-9-2.txt:11(搜「与其角色不符」) · text/56-ch09-02-9-2.txt:13(搜「getCustomers」)
反模式流行度与工具9.2 低质量标识符对认知负荷的影响text/56-ch09-02-9-2.txt:19(搜「64%」) · text/56-ch09-02-9-2.txt:23(搜「LAPD」)
帕斯量表9.2 低质量标识符对认知负荷的影响text/56-ch09-02-9-2.txt:33(搜「帕斯量表」) · text/56-ch09-02-9-2.txt:37(搜「诟病」)
眨眼率与瞳孔9.2 低质量标识符对认知负荷的影响text/56-ch09-02-9-2.txt:51(搜「眨眼率」)
fNIRS 原理9.2 低质量标识符对认知负荷的影响text/56-ch09-02-9-2.txt:71(搜「氧化血红蛋白」) · text/56-ch09-02-9-2.txt:73(搜「运动伪影」)
中川 2014 实验9.2 低质量标识符对认知负荷的影响text/56-ch09-02-9-2.txt:77(搜「有意复杂化」) · text/56-ch09-02-9-2.txt:81(搜「10位被试者中有8人」)
Fakhoury 四组实验9.2 低质量标识符对认知负荷的影响text/56-ch09-02-9-2.txt:85(搜「15位被试者」) · text/56-ch09-02-9-2.txt:99(搜「更关注」) · text/56-ch09-02-9-2.txt:101(搜「显著增加」) · text/56-ch09-02-9-2.txt:105(搜「没有统计证据」)
反模式为何骗人9.2 低质量标识符对认知负荷的影响text/56-ch09-02-9-2.txt:111(搜「错误的印象」) · text/56-ch09-02-9-2.txt:113(搜「分块错误」)

Footnotes

  1. 出处:「9.1 为什么存在异味的代码会加重认知负荷」第 5、13 段(text/55-ch09-01-9-1.txt:5,搜「Martin Fowler」;text/55-ch09-01-9-1.txt:13,搜「22种代码异味」)。

  2. 出处:「9.1 为什么存在异味的代码会加重认知负荷」第 51 段(text/55-ch09-01-9-1.txt:51,搜「不同类型的认知过程」)。按认知机制重新分组为作者的诠释,原文的分组见第 53–79 段。

  3. 出处:「9.1 为什么存在异味的代码会加重认知负荷」第 55 段(text/55-ch09-01-9-1.txt:55,搜「超过6个」)。

  4. 出处:「9.1 为什么存在异味的代码会加重认知负荷」第 59–63 段(text/55-ch09-01-9-1.txt:63,搜「4个组块」)。line() 方法示例见第 59–61 段。

  5. 出处:「9.1 为什么存在异味的代码会加重认知负荷」第 67 段(text/55-ch09-01-9-1.txt:67,搜「见名知意」)。

  6. 出处:「9.1 为什么存在异味的代码会加重认知负荷」第 75–79 段(text/55-ch09-01-9-1.txt:79,搜「西西里防御的几种变例」)。

  7. 出处:「9.1 为什么存在异味的代码会加重认知负荷」第 37、39 段(text/55-ch09-01-9-1.txt:37,搜「易错性」;text/55-ch09-01-9-1.txt:39,搜「75%」)。

  8. 出处:「9.2 低质量标识符对认知负荷的影响」第 11 段(text/56-ch09-02-9-2.txt:11,搜「与其角色不符」)。

  9. 出处:「9.2 低质量标识符对认知负荷的影响」第 19 段(text/56-ch09-02-9-2.txt:19,搜「64%」)。三个数字(11%、2.5%、64%)同段。

  10. 出处:「9.2 低质量标识符对认知负荷的影响」第 23 段(text/56-ch09-02-9-2.txt:23,搜「LAPD」)。

  11. 出处:「9.2 低质量标识符对认知负荷的影响」第 85 段(text/56-ch09-02-9-2.txt:85,搜「15位被试者」)。研究者为华盛顿州立大学博士生 Sarah Fakhoury,2018 年;四种代码片段见第 87–95 段。 2

  12. 出处:「9.2 低质量标识符对认知负荷的影响」第 97 段(text/56-ch09-02-9-2.txt:97,搜「学习效应」)。

  13. 出处:「9.2 低质量标识符对认知负荷的影响」第 99、105 段(text/56-ch09-02-9-2.txt:99,搜「更关注」;text/56-ch09-02-9-2.txt:105,搜「没有统计证据」)。脑血氧结果见第 101 段。

  14. 出处:「9.2 低质量标识符对认知负荷的影响」第 111 段(text/56-ch09-02-9-2.txt:111,搜「错误的印象」)。retrieveElements 例子同段。

  15. 出处:「9.2 低质量标识符对认知负荷的影响」第 113 段(text/56-ch09-02-9-2.txt:113,搜「分块错误」)。

  16. 出处:「9.2 低质量标识符对认知负荷的影响」第 33–73 段(text/56-ch09-02-9-2.txt:33,搜「帕斯量表」)。帕斯量表的不足见第 37 段;眨眼率与瞳孔见第 51 段;fMRI 局限见第 63 段;脑电图与 fNIRS 见第 67–73 段。

  17. 出处:「9.2 低质量标识符对认知负荷的影响」第 77、81 段(text/56-ch09-02-9-2.txt:81,搜「10位被试者中有8人」)。

  18. 出处:「9.1 为什么存在异味的代码会加重认知负荷」第 49 段(text/55-ch09-01-9-1.txt:49,搜「并没有把二者联系起来」)。