主题
认知负荷与可读性:编码心理学 (Cognitive Load & Readability)
"Programs must be written for people to read, and only incidentally for machines to execute." — Hal Abelson
软件开发的真正瓶颈不是 CPU 的时钟频率,也不是网络的带宽,而是开发者的脑容量。
作为人类,我们的工作记忆(Working Memory)极其有限。当我们试图理解一段复杂的代码时,我们的大脑就像一台只有 8KB 内存的计算机。一旦变量、逻辑分支或上下文的数量超过了我们的处理极限,我们就会"堆栈溢出"(Stack Overflow),导致理解失败或引入 Bug。
本章将从心理学的角度,探讨如何编写"对大脑友好"的代码。
1. 米勒定律 (Miller's Law) 与 7±2
心理学家 George Miller 在 1956 年提出,人类的短期记忆只能同时容纳约 7 个(±2)"组块"(Chunks)。
这对代码意味着什么?
- 函数长度:如果一个函数有 50 行,包含 20 个局部变量,它绝对超过了 7 个组块。读者必须不断上下滚动,试图记住
tempVar1到底是什么。 - 嵌套深度:每一层
if或for嵌套,都占用了一个记忆槽位("我现在是在循环里,且满足条件 A,且不满足条件 B...")。超过 3 层嵌套,大多数人就开始晕了。
架构启示: 模块化(Modularity)的本质,就是为了应对米勒定律。我们将 1000 行代码封装成一个函数 processOrder(),就是将 1000 个细节压缩成了 1 个组块。
2. 认知负荷的三种类型
在教学设计理论中,认知负荷被分为三类,这完全适用于编程:
A. 内部负荷 (Intrinsic Load)
这是任务本身固有的难度。
- 例子:实现一个复杂的加密算法或红黑树。
- 对策:这是无法消除的,但可以通过良好的封装(Deep Modules)来隔离。
B. 外在负荷 (Extraneous Load) 🔥 (这是我们要消灭的敌人)
这是由于呈现方式糟糕而产生的额外负担。
- 例子:
- 糟糕的命名(
var a, b, c)。 - 混乱的格式化。
- 不必要的全局变量。
- "面条式代码"(Spaghetti Code)。
- 糟糕的命名(
- 对策:重构、重命名、一致的代码风格。外在负荷是软件工程的主要战场。
C. 关联负荷 (Germane Load)
这是用于构建图式(Schema)和理解模式的精力。
- 例子:学习设计模式或框架约定。
- 对策:使用通用的设计模式(如 MVC, Observer),利用开发者已有的知识图谱。
3. 局部性原理 (Local Reasoning)
局部推理 (Local Reasoning) 是代码可读性的圣杯。
它指的是:读者能否仅通过阅读当前函数或文件,就完全理解其行为,而不需要跳转到其他文件?
- 破坏局部性的杀手:
- 全局状态 (Global State):你不知道谁在什么时候修改了这个变量。
- 副作用 (Side Effects):调用
getUser()竟然修改了数据库? - 依赖注入滥用:逻辑被分散在 XML 配置或注解魔法中,看不见摸不着。
规则:让代码的逻辑流像水一样顺流而下,尽量减少"隐形通道"。
4. 邓巴数字 (Dunbar's Number) 与代码库规模
人类能维持的稳定社交关系上限约为 150 人(邓巴数字)。 类似地,一个开发者能深入理解的代码模块数量也是有限的。
当代码库规模超过某个临界点,就没有人能理解全貌了。 这时,清晰的边界 (Boundaries) 变得生死攸关。 微服务、插件架构、分层架构,本质上都是为了将庞大的系统切割成小于邓巴数字的子系统,让不同团队可以独立维护。
5. 编写"无聊"的代码
很多资深工程师最终都回归到了"极简主义"。 他们不再使用晦涩的语言特性(如 C++ 的模板元编程,JS 的原型链魔法),而是倾向于写"笨"代码。
"Clever Code" (聪明代码): 利用语言的边缘特性,用一行代码解决复杂问题。写的时候很爽,读的时候想死。
"Boring Code" (无聊代码): 显式、直白、没有惊喜。初级工程师也能一眼看懂。
记住:调试代码比编写代码难两倍。如果你发挥了最高智商写代码,那你将没有足够的智商去调试它。 (Brian Kernighan)
结语:同理心驱动开发 (Empathy-Driven Development)
可读性不是关于缩进或命名规范,它是关于同理心。 当你写代码时,想象一下:三个月后,一个疲惫不堪、急着修 Bug 的你,看到这段代码时会是什么心情?
如果你能感受到那个未来的你的痛苦,你现在就会把那个变量名改得更清晰一点。