给代码库做认知体检 — 十三个体检项与它们的权衡
这一章讲三件事: 怎么从「对大脑的影响」评价一门语言、一个框架、一个代码库; 十三个体检项各自在问什么;以及为什么体检报告的结论几乎总是「权衡」而不是「改进」。 它把前十二章的个人视角拉高到系统视角:你写的代码,是别人大脑的工作环境。
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. 可带走的
- 选型/评审时把「认知租金」和「技术指标」分开列——技术好认知差的库,接手成本藏在后面;
- 名字贴近领域(execute_query → find_inactive_customers)是少数「纯赚」的策略,优先做;
- 任何「防错」设计先问一句:它把负担挪到哪了?——类型、校验、断言都有账单;
- 给内部库配「宽松模式」,给新人留出写半截代码的空间;
- 测试太慢本身就是认知问题(黏性),拆快慢层优先级高于大多数重构;
- 给 API 的返回值选类型时想想可见性:对象 > JSON(带名称标签的文本格式)> 裸字符串;
- 团队体检按使用场景排优先级:老库保搜索辅助,新库保递增顺畅;
- 每年把十三项过一遍——偏差是慢性的,一年一次刚好。
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.1 | text/70-ch12-02-12-2.txt:41(搜「生命周期」) · text/69-ch12-01-12-1.txt:209(搜「每年一次」) |