跳到主要内容

给代码库做认知体检 — 十三个体检项与它们的权衡

这一章讲三件事: 怎么从「对大脑的影响」评价一门语言、一个框架、一个代码库; 十三个体检项各自在问什么;以及为什么体检报告的结论几乎总是「权衡」而不是「改进」。 它把前十二章的个人视角拉高到系统视角:你写的代码,是别人大脑的工作环境。

1. 全景:从技术视角换到认知视角

平时怎么评价一个库?「Python 写的」「支持异步」「编译成字节码」——全是技术属性,回答的是「它能做什么」。这一章换一副眼镜:它让使用者的大脑多费多少劲。这套体检表叫符号认知维度,由 Green、Blackwell 与 Petre 提出,原本用于评估编程语言这类「符号系统」;作者把它推广到代码库层面,叫代码库认知维度——当你是在别人的库(而不是改它)时,这个视角尤其要紧1

技术视角:这门语言/这个库 能做什么?
认知视角:用它的人 大脑要付多少租金?

十三个体检项(维度),逐个过 → 发现短板 → 应用「设计策略」

但是:维度之间互相拉扯,改动几乎总要付代价(第 3 节)

图说:这一章的结构就是「体检表 + 体检报告永远不完美」。

2. 十三个体检项

十三项体检,每项都在问一种「代码世界和真实世界的对应」关系(这种对应,术语叫映射):

① 易错性——多容易写错。动态弱类型的 JavaScript 声明变量不写类型,类型错误要等到运行时才炸;静态强类型的 Haskell 在你写的时候就拦2。类型系统真能防错吗?哈嫩贝格(Hanenberg)拿 Java(静态)对比 Groovy(动态)做了大量实验:有类型时,程序员找错更快更准;给动态语言配上再好的 IDE 和文档,也追不回来3。代码库从所用语言继承易错性,库自身的约定不一、没文档、名字含义不明也会抬高它。

**② 一致性——相似的东西有多像。**内置函数和自定义函数从声明上分不出来,这是一致性好的表现;命名模具满天飞的库则相反。不一致的大脑代价:每个元素都要重新梳理关系,检索慢、负荷高(第 09 章的「一致派」在这里升级成系统级指标)4

**③ 扩散性——同样的意思占多少地方。**同一个 for 循环,Python 两行,C++ 三行;再数组块:Python 版 7 个元素,C++ 版 9 个——占地方的不只是行数,是组块数5。同语言内部也同理:列表推导式比展开的 for 循环扩散性低6

**④ 隐藏的依赖关系——依赖可不可见。**HTML 页面上一个由另一个文件里的 JS 控制的按钮,你看 JS 文件时根本想不到它;「这个函数被谁调用」通常可见,反向的「谁在等这个文件」往往不可见。IDE 能帮着查,但每次查都在花工作记忆7

**⑤ 临时性——想法能不能先随便画。**纸笔白板是临时性之王:画错就擦,没人检查语法。代码库的临时性体现在「能不能先写不完整的想法」——类型、断言、前置条件越严,临时性越低。对新人是关键项:初学者只能写出不完整的代码,逼他一次写完整,等于在思考的同时还要伺候语法8

**⑥ 黏性——改起来多费劲。**动态语言的库改起来轻(类型声明不用动);编译慢、测试慢的库,每改一次都付一次等待税,黏性就高9

**⑦ 渐进式评估——能不能跑半截。**Smalltalk 和 Scratch 可以不中断运行就改代码;Idris 语言可以挖一个「坑」——先跑起来,让编译器告诉你坑里该填什么类型,迭代着填。库的设计也能支持:带默认值的可选参数,让用户先跑通、再逐个调10

**⑧ 角色可表达性——每个元素亮不亮身份。**函数末尾的括号宣告「我是函数」,语法高亮区分关键字和变量,is_set 这样的名字自报家门——反过来,第 10 章的语言反模式就是角色可表达性差的标准病例11

**⑨ 映射紧密性——代码和问题领域有多贴。**APL 与向量(带方向的数组)运算贴(COBOL 与金融贴,Excel 的行列与纸面账本贴);findCustomers() 贴,executeQuery() 不贴。大多数通用语言刻意不贴——一门语言包打所有领域,代价就是每个领域都要自己搭词汇;近年兴起的领域驱动设计,本质上是主动把映射紧密性调高12

⑩ 艰难的心理操作——有没有强迫用户心算的重活。记 8 个参数还要按顺序传、从两个来源下载数据再转成第三种格式、盯着 execute() 这种名字猜它在干嘛——全是把负荷硬塞给用户的大脑13。艰难操作不全是坏事(严格类型换来防错就是交易),但要自知:你把多少心算外包给了用户。

**⑪ 辅助符号——正式语法之外的信息通道。**注释是最典型的:不参与执行,纯给大脑看。Python 的命名参数也算——move(angle=90, power=100) 顺序写反也没错,因为名字就是给读的人留的路标14

**⑫ 抽象——用户能不能造自己的积木。**能自定义函数、类、子类的系统,用户可以按自己的想法塑形语言;只能调 API 的库,抽象能力就弱15

**⑬ 可见性——系统的部分难不难看到。**类散在 40 个文件里,「这个库有哪些类」就成了难题;API 返回裸字符串时,数据长什么样全靠猜16

3. 主走查:给一个小库出体检报告

拿一个虚构但具体的例子走一遍流程(库与体检结论为演示而编,体检项的判据全部来自原书)17:假设团队有个内部库 customer-utils,查客户、筛客户、算客户价值。体检:

体检对象:customer-utils v2.3(演示用虚构库)
使用场景:团队新人接入最频繁 → 按生命周期原则,优先保「理解/递增」

① 易错性 中:Python 写的,类型松;查询函数返回值有时是列表有时是 None
⑨ 映射紧密性 差:核心函数叫 execute_query() —— 查的是客户,名字里没有客户
→ 改名 find_inactive_customers(),这是教科书级的正例(原书同款)
⑩ 艰难心理操作 高:get_value() 要记住 5 个参数的顺序
→ 加关键字参数(辅助符号⑪顺手改善),顺序不再是心算题
⑤ 临时性 低:内部校验严格,新人没法先写半截试试
→ 加一个「宽松模式」开关,试验性代码先跑通再补校验
⑬ 可见性 差:40 个文件没有索引文档 → 门槛项,先补
⑥ 黏性 高:测试套件跑一轮 40 分钟 → 每改一次付 40 分钟
→ 拆快慢测试层(这不是维度改造,是改造的前置)

报告结论:先修映射紧密性和艰难操作(纯赚,无明显代价),
临时性要与易错性一起权衡(见下节)。

走查里每一项的判据都有出处:execute_query 对照 findCustomers 见原书12,参数顺序属艰难操作见原书13,可选参数支持渐进式评估见原书10。体检的产出是「排序过的短板清单」——按使用场景排序,这正是下一节的要点。

4. 体检之后:权衡是常态,改进是交易

针对某维度做的调整叫设计策略——给实体加类型是改善易错性的策略,把函数名改成领域词是改善映射紧密性的策略18。麻烦在于:策略几乎总要付账,书里点了三对最常见的拉扯19:

你想要的代价
低易错性(加类型系统)高黏性——用户得伺候类型转换,改造变重
高临时性+渐进式评估(随便写随便跑)高易错性——不完整的代码忘了删,就是隐患
高角色可表达性(加记号、加注释)高扩散性——代码越长,读的行数越多

这解释了一类常见的团队争执:静态类型派和动态自由派吵的其实是同一份租金记在哪本账上——没有免租的选项。

最后一层:按活动选改善对象。第 12 章的五种活动,各吃不同的侧面——搜索怕隐藏依赖、爱辅助符号;理解要角色可表达性;转写怕一致性(新代码必须迁就既有约定,写时要额外费心);递增要映射紧密性、怕黏性;探索要临时性和渐进式评估,怕艰难操作和抽象负担20。落到运营:老而稳定的库,用户以搜索为主,把辅助符号做好最划算;新应用,递增频繁,优先保映射紧密性和低黏性——代码库的一生里,体检结论会随生命周期换结论21。书里的节律建议:每年过一遍体检项22

5. 作者的判断与证据

**有实验撑着的:**类型系统防错(Hanenberg,Java 对 Groovy 的系列实验);维度框架本身有 HCI(人机交互)领域二十多年的积累,13 项不是作者杜撰——但「推广到代码库」这一步是作者本人做的,原框架评的是语言。

作者的发挥,要分开看:「代码库认知维度」这个改编、三对权衡的举例、按生命周期选改善对象的建议,都是作者的引申;维度与活动的对应表是作者对原框架文献的整理。

书的坦白:「并非每个维度都会影响所有代码库」——体检表是菜单,不是考卷;哪些项重要由使用场景说了算。

6. 边界与局限

  • 十三个维度是描述性的,没有给出度量:多少算「黏性高」?40 分钟测试算高,那 10 分钟呢?量表要团队自己定;
  • 维度框架源于对编程语言与可视化系统的研究,搬到代码库层面后没有系统性的效度验证——它是思考清单,不是科学仪器;
  • 权衡表给的是「常见拉扯」,具体的此消彼长方式与代码库强相关,原书明确说「确切方式与代码库密切相关」;
  • 本章站在「库作者」的视角;如果你只是库的用户,这一章的用法是反过来的:用维度给候选库做选型对比。

7. 可带走的

  1. 选型/评审时把「认知租金」和「技术指标」分开列——技术好认知差的库,接手成本藏在后面;
  2. 名字贴近领域(execute_query → find_inactive_customers)是少数「纯赚」的策略,优先做;
  3. 任何「防错」设计先问一句:它把负担挪到哪了?——类型、校验、断言都有账单;
  4. 给内部库配「宽松模式」,给新人留出写半截代码的空间;
  5. 测试太慢本身就是认知问题(黏性),拆快慢层优先级高于大多数重构;
  6. 给 API 的返回值选类型时想想可见性:对象 > JSON(带名称标签的文本格式)> 裸字符串;
  7. 团队体检按使用场景排优先级:老库保搜索辅助,新库保递增顺畅;
  8. 每年把十三项过一遍——偏差是慢性的,一年一次刚好。

8. 原文地图

主题原书章原文位置
框架起源与推广12.1 代码库的属性text/69-ch12-01-12-1.txt:23(搜「符号认知维度」) · text/69-ch12-01-12-1.txt:25(搜「代码库认知维度」)
易错性与类型实验12.1 代码库的属性text/69-ch12-01-12-1.txt:33(搜「静态强类型」) · text/69-ch12-01-12-1.txt:41(搜「Java与Groovy」) · text/69-ch12-01-12-1.txt:43(搜「不及静态类型系统」)
一致性12.1 代码库的属性text/69-ch12-01-12-1.txt:47(搜「一致性(consistency)」) · text/69-ch12-01-12-1.txt:51(搜「增加认知负荷」)
扩散性12.1 代码库的属性text/69-ch12-01-12-1.txt:59(搜「扩散性(diffuseness)」) · text/69-ch12-01-12-1.txt:72(搜「7个元素」) · text/69-ch12-01-12-1.txt:86(搜「扩散性低于」)
隐藏的依赖关系12.1 代码库的属性text/69-ch12-01-12-1.txt:90(搜「隐藏的依赖关系」) · text/69-ch12-01-12-1.txt:98(搜「记录」)
临时性12.1 代码库的属性text/69-ch12-01-12-1.txt:102(搜「临时性(provisionality)」) · text/69-ch12-01-12-1.txt:108(搜「可学习性」)
黏性12.1 代码库的属性text/69-ch12-01-12-1.txt:112(搜「黏性(viscosity)」) · text/69-ch12-01-12-1.txt:114(搜「编译或运行测试」)
渐进式评估12.1 代码库的属性text/69-ch12-01-12-1.txt:118(搜「渐进式评估」) · text/69-ch12-01-12-1.txt:120(搜「Smalltalk」) · text/69-ch12-01-12-1.txt:124(搜「坑」)
角色可表达性12.1 代码库的属性text/69-ch12-01-12-1.txt:130(搜「role-expressiveness」) · text/69-ch12-01-12-1.txt:136(搜「语言反模式」)
映射紧密性12.1 代码库的属性text/69-ch12-01-12-1.txt:140(搜「映射紧密性」) · text/69-ch12-01-12-1.txt:146(搜「COBOL」) · text/69-ch12-01-12-1.txt:150(搜「executeQuery」) · text/69-ch12-01-12-1.txt:152(搜「领域驱动设计」)
艰难的心理操作12.1 代码库的属性text/69-ch12-01-12-1.txt:166(搜「艰难的心理操作」) · text/69-ch12-01-12-1.txt:172(搜「正确的顺序」) · text/69-ch12-01-12-1.txt:174(搜「execute()」)
辅助符号12.1 代码库的属性text/69-ch12-01-12-1.txt:180(搜「辅助符号(secondary notation)」) · text/69-ch12-01-12-1.txt:191(搜「命名参数」)
抽象与可见性12.1 代码库的属性text/69-ch12-01-12-1.txt:195(搜「抽象(abstraction)」) · text/69-ch12-01-12-1.txt:201(搜「可见性(visibility)」)
设计策略与三对权衡12.1 代码库的属性text/69-ch12-01-12-1.txt:219(搜「设计策略(design maneuver)」) · text/69-ch12-01-12-1.txt:229(搜「易错性与黏性」) · text/69-ch12-01-12-1.txt:237(搜「临时性较高」) · text/69-ch12-01-12-1.txt:239(搜「角色可表达性与扩散性」)
维度×活动12.2 认知维度和编程活动text/70-ch12-02-12-2.txt:15(搜「隐藏的依赖关系不利于搜索」) · text/70-ch12-02-12-2.txt:27(搜「一致性」) · text/70-ch12-02-12-2.txt:35(搜「临时性」)
生命周期与定期体检12.2 认知维度和编程活动 / 12.1text/70-ch12-02-12-2.txt:41(搜「生命周期」) · text/69-ch12-01-12-1.txt:209(搜「每年一次」)

Footnotes

  1. 出处:「12.1 代码库的属性」第 23 段(text/69-ch12-01-12-1.txt:23,搜「符号认知维度」)与第 25 段(text/69-ch12-01-12-1.txt:25,搜「代码库认知维度」)。推广到代码库为作者本人的做法。

  2. 出处:「12.1 代码库的属性」第 33 段(text/69-ch12-01-12-1.txt:33,搜「静态强类型」)。

  3. 出处:「12.1 代码库的属性」第 41、43 段(text/69-ch12-01-12-1.txt:41,搜「Java与Groovy」;text/69-ch12-01-12-1.txt:43,搜「不及静态类型系统」)。

  4. 出处:「12.1 代码库的属性」第 47、51 段(text/69-ch12-01-12-1.txt:47,搜「一致性(consistency)」)。

  5. 出处:「12.1 代码库的属性」第 59、72 段(text/69-ch12-01-12-1.txt:59,搜「扩散性(diffuseness)」;text/69-ch12-01-12-1.txt:72,搜「7个元素」)。Python for 循环 7 个组块、C++ 9 个。

  6. 出处:「12.1 代码库的属性」第 86 段(text/69-ch12-01-12-1.txt:86,搜「扩散性低于」)。

  7. 出处:「12.1 代码库的属性」第 90–94 段(text/69-ch12-01-12-1.txt:90,搜「隐藏的依赖关系」)。

  8. 出处:「12.1 代码库的属性」第 102、108 段(text/69-ch12-01-12-1.txt:102,搜「临时性(provisionality)」;text/69-ch12-01-12-1.txt:108,搜「可学习性」)。

  9. 出处:「12.1 代码库的属性」第 112、114 段(text/69-ch12-01-12-1.txt:112,搜「黏性(viscosity)」)。

  10. 出处:「12.1 代码库的属性」第 118–124 段(text/69-ch12-01-12-1.txt:118,搜「渐进式评估」)。Smalltalk 实时编程见第 120 段,Idris 的坑与可选参数见第 124 段。 2

  11. 出处:「12.1 代码库的属性」第 130、136 段(text/69-ch12-01-12-1.txt:130,搜「role-expressiveness」;text/69-ch12-01-12-1.txt:136,搜「语言反模式」)。

  12. 出处:「12.1 代码库的属性」第 140、146、150、152 段(text/69-ch12-01-12-1.txt:150,搜「executeQuery」;text/69-ch12-01-12-1.txt:152,搜「领域驱动设计」)。 2

  13. 出处:「12.1 代码库的属性」第 166–176 段(text/69-ch12-01-12-1.txt:166,搜「艰难的心理操作」)。参数顺序见第 172 段,execute()/control() 见第 174 段。 2

  14. 出处:「12.1 代码库的属性」第 180–191 段(text/69-ch12-01-12-1.txt:180,搜「辅助符号(secondary notation)」)。命名参数示例见第 184–189 段。

  15. 出处:「12.1 代码库的属性」第 195 段(text/69-ch12-01-12-1.txt:195,搜「抽象(abstraction)」)。

  16. 出处:「12.1 代码库的属性」第 201–203 段(text/69-ch12-01-12-1.txt:201,搜「可见性(visibility)」)。

  17. 主走查的 customer-utils 库与体检结论为本拆解虚构;所引判据分别见脚注 10、12、13 对应的原文段落。

  18. 出处:「12.1 代码库的属性」第 219 段(text/69-ch12-01-12-1.txt:219,搜「设计策略(design maneuver)」)。

  19. 出处:「12.1 代码库的属性」第 229–241 段(text/69-ch12-01-12-1.txt:229,搜「易错性与黏性」)。三对权衡分别见第 231、237、241 段。

  20. 出处:「12.2 认知维度和编程活动」第 15–37 段(text/70-ch12-02-12-2.txt:15,搜「隐藏的依赖关系不利于搜索」)。搜索见第 15–17 段,理解见第 21–23 段,转写见第 27 段,递增见第 31 段,探索见第 35–37 段。

  21. 出处:「12.2 认知维度和编程活动」第 41 段(text/70-ch12-02-12-2.txt:41,搜「生命周期」)。

  22. 出处:「12.1 代码库的属性」第 209 段(text/69-ch12-01-12-1.txt:209,搜「每年一次」)。