三种记忆,三种困惑 — 代码为什么读不懂的第一层答案
这一章讲三件事: 读代码时大脑内部在动用哪三套装置;「卡壳」为什么至少有三种不同口味; 以及怎么判断自己此刻卡在哪一种。这一章是全书的字典——后面十二章讲的每一个办法, 都是在救这三套装置中的一套。不需要任何基础,遇到的生词都在当场解释。
1. 全景:三段同样功能的代码,三种不同的卡法
先看现象。下面三段程序做的事一模一样:把一个数换算成二进制写出来(二进制就是只用 0 和 1 记数,就像 13 要写成 1101)。但读它们的感受完全不同1:
APL(一门1960年代的数学语言): 2 2 2 2 2 ⊤ n
Java: 一小段标准骨架,中间调用 Integer.toBinaryString(n)
BASIC: 9 行循环,自己动手算每一位
图说:同一个任务,三种语言。读第一段时你不知道符号什么意思,
读第二段时你知道方向但不知道那个方法干什么,读第三段时每一行都懂、合起来跟不住。
第一段的卡点:那个 ⊤ 符号你没见过。第二段的卡点:toBinaryString 这个方法做什么,得去别处查。第三段的卡点最隐蔽——每个词你都认识,但你记不住执行到一半时各个东西的值23。
这三种卡壳不是同一种困惑的三种程度,而是三种不同的困惑。它们的根源分别长在大脑的三套不同装置上——大脑里管「记住东西、拿来想问题」的装置,本书统称为记忆,记忆一共分三套。这就是本章的主张:
| 困惑 | 口语版 | 对应的大脑装置 |
|---|---|---|
| 缺知识 | 「这个词/符号我没见过」 | 长时记忆 |
| 缺信息 | 「我得去别处查一下」 | 短时记忆 |
| 缺加工能力 | 「每步都懂,合起来跟不住」 | 工作记忆 |
这张表是全书的地基。接下来三节分别把三套装置讲清楚,第 5 节拿第三段代码从头走一遍,让你亲眼看到「加工能力不足」是怎么发生的。
2. 长时记忆:缺的是知识
长时记忆就是大脑里保存信息几个月、几年甚至一辈子的那部分。你会系鞋带、记得二分搜索怎么写、记得键盘上每个键在哪,靠的都是它4。它连具体数字都存:Java 里 int 型变量——变量就是代码给一个值起的名字,后面靠这个名字反复取用——能存下的最大值是 2147483647,这类事实一旦记住,想用的时候直接就来了5。
读那段 APL 代码时,起决定作用的就是长时记忆:认识 ⊤ 的人一眼扫过,不认识的人寸步难行。**同样的代码,对不同的人难度完全不同——因为长时记忆里的存货不同。**这是三套装置里唯一能靠积累变富的一套,第 3 章专门讲怎么往里存东西。
一个马上会用到的判断:如果卡你的东西是「这个词什么意思」「这个函数是干什么的」——是知识问题,该去查、去学、去记,而不是硬盯着看。
3. 短时记忆:缺的是信息
短时记忆就是大脑临时抄便签的那块地方:刚刚听到的电话号码、三行代码前看到的变量名,先放在这里6。它有两个致命属性:
第二段 Java 代码考验的就是它。你读到第二行知道「n 是个整数」,这个事实先放进短时记忆,撑到你读到第四行、弄明白 toBinaryString 在做什么为止。任务完成,清空。
这一节还有个精彩的细节。示例代码把 main 有意错拼成了 mian,但熟悉 Java 的人根本没注意到——大脑用长时记忆里的「这里应该是 main」直接盖过了短时记忆里实际收到的「mian」9。两种装置不是各干各的:长时记忆会不声不响地替短时记忆「纠错」。
判断(我们的,不是书里的): 这条「抄近道」其实是一把双刃剑的正式命名。 读代码快,靠的是它——熟练工不用逐字读;而「我看代码时把想当然的东西看成了实际写的东西」, 也靠的是它。review 别人代码时抓不出错、自己复制粘贴改了半天没改全, 有一部分根源就在这里:你看到的不是屏幕上的字,是长时记忆里那个「应该」。 如果错,会错在: 如果错拼恰好因为生僻而更容易被发现(生词没有近道可抄), 那么这条解释只适用于「长得像正确答案」的错字,而不是一切错字。
4. 工作记忆:缺的是加工能力
前两套装置都只管「存」:长时记忆存久的,短时记忆存刚来的。但思考这件事发生在第三套装置里——工作记忆。它是大脑的处理器:新想法、新方案,都在这里被加工出来10。
它的加工容量同样小得可怜,一次大约只能摆开五六个东西。所以当一段代码需要你同时记住五六个中间结果、还要往上叠新的推理(推理就是从已知条件一步一步推向结论的思考)时,工作记忆就顶不住了——第三段 BASIC 代码就是为这个设计的。
书里给了一个非常实用的自检信号:当你发现自己需要把中间结果写到纸上演算,或者需要拿手指着代码一行行跟,这说明工作记忆已经过载——不是你不够聪明,是这段代码塞给处理器的活儿超了11。这个信号第 4 章会变成一整套应对办法。
5. 主走查:亲手把一段简单代码跑进工作记忆
**这一节拿第三段 BASIC 代码从头走一遍。**它只有 9 行,功能是求一个数的二进制表示。我们取 N=13(这个输入值是我为演示挑的,书里用的是抽象的 N;下面的每一步结果都是从代码里一步一步算出来的,不是编的):
1 LET N2 = ABS(INT(N)) N2 = 13
2 LET B$ = "" B$ = "" (B$ 攒结果用)
3 FOR N1 = N2 TO 0 STEP 0 N1 = 13
4 LET N2 = INT(N1 / 2) N2 = INT(13/2) = 6
5 LET B$ = STR$(N1-N2*2) + B$ ↓
现在开始逐行执行,盯着三个东西同时在变:
| 轮次 | 进来时 | 第 4 行算出 N2 | 第 5 行拼进 B$ | 此刻手里要同时端着 |
|---|---|---|---|---|
| 第 1 遍 | N1=13 | N2 = 6 | 13−6×2 = 1,B$ = "1" | N1=13、N2=6、B$="1",还有「下一遍用 N2 当 N1」这条规矩 |
| 第 2 遍 | N1=6 | N2 = 3 | 6−3×2 = 0,B$ = "01" | N1=6、N2=3、B$="01" |
| 第 3 遍 | N1=3 | N2 = 1 | 3−1×2 = 1,B$ = "101" | N1=3、N2=1、B$="101" |
| 第 4 遍 | N1=1 | N2 = 0 | 1−0×2 = 1,B$ = "1101" | N1=1、N2=0、B$="1101" |
走到第四遍,B$ 里攒出的 "1101" 正是 13 的二进制。但你注意「此刻手里要同时端着」那一列:每一轮你都得同时记住两个数的当前值、一个正在拼的字符串(即一串字 符排成的文本,B$ 攒的就是它),外加一条「N2 回填给 N1」的循环规矩——这是四个东西,而且它们每一遍都在变。
这正是工作记忆的容量上限。**你不是看不懂任何一行,你是装不下。**书里给出的办法和上表做的是同一件事:把中间值写在代码旁边(我写成了表格),让纸替大脑端着这几个值12。
顺便把这条走查里每个机制的角色点一下名:
- 「B$ 是什么、N1 现在是几」——存进短时记忆的是刚刚接收的值;
- 「STR$ 负责把数转成文字、INT 负责砍掉小数」——从长时记忆里取的语法(一门语言规定的书写规则,什么操作用什么写法)知识;
- 「下一遍 N2 回填 N1、循环什么时候停」——工作记忆在加工的推理(即从已知条件一步一步推向结论的思考)。
一段 9 行的代码,三套装置全数到场。任何思考活动其实都是这样:三套装置同时开工,只是各干的活不同13。
6. 作者的判断与证据
**书里当证据给的:**三种认知过程的划分来自认知科学的主流模型,不是作者拍的;本章只用三段代码做了现象层面的演示,真正的实验证据在第 2 章(棋手与程序员记忆实验)和第 3 章(遗忘曲线)。
**书里明说不知道的:**短时记忆到底能装几个,书里前后给过两个口径——第 1 章说「通常不会超过 12 个」,第 2 章引近年研究说「大约 26 个」。这不是书自相矛盾,而是这个数字在学界本来就有争议。本书后续章节按 26 的口径使用。读的时候别拿 12 和 6 抬杠,拿「个位数」这个量级用就行。
**作者自己的主张(要和实验证据分开):**把工作记忆定义为「应用于某个问题的短时记忆」、把三者的比喻定为硬盘/内存/处理器,这是作者为了讲解做的取舍;学界对「工作记忆和短时记忆是不是一回事」其实有分歧(第 4 章会正面处理)。
7. 边界与局限
- **三种困惑的分界在真实代码里常常糊在一起。**读一段烂代码,可能同时缺知识、缺信息、又超负荷。本章教的是「先分诊再下药」,不是「每次只有一种病」。
- **60 年代的 APL 例子是个极端样本。**它的作用是让「缺知识」看得见摸得着;今天的读者更常见的知识缺口是框架和领域概念,机制相同。
- **比喻(硬盘/内存/处理器)只是脚手架。**大脑不是真的这样存取,这个比喻到第 3 章讲遗忘时就会出现第一个裂缝:长时记忆可不像硬盘,写进去就丢不了。
8. 可带走的
- 卡壳先分诊:没见过(缺知识)、要查(缺信息)、跟不住(缺加工),三种病三种药;
- 长时记忆是唯一能靠积累变富的一套——值得花时间往里存,第 3 章讲怎么存;
- 短时记忆又小又快扔——所以「刚才查到什么」转瞬就忘,不是你态度差;
- 长时记忆会替你「纠错」,所以你看到的代码不一定是屏幕上的代码——review 时要刻意放慢;
- 需要纸笔记中间值 = 工作记忆过载的官方信号,这时候该做的是给代码减负或换工具,不是骂自己;
- 别拿「短时记忆能装几个」的具体数字当真理,拿「个位数」这个量级做决策就够。
9. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 困惑司空见惯、本章目标 | 第1章 剖析程序设计之惑 | text/08-ch01.txt:13(搜「司空见惯」) · text/08-ch01.txt:17(搜「不同类型的困惑」) |
| 三段代码、三种卡法 | 1.1 代码造成的各种困惑 | text/09-ch01-01-1-1.txt:5(搜「3段代码分别采用APL」) · text/09-ch01-01-1-1.txt:9(搜「困惑之处在于程序员不一定清楚」) |
| 缺信息、中间值上纸 | 1.1 代码造成的各种困惑 | text/09-ch01-01-1-1.txt:63(搜「缺乏信息」) · text/09-ch01-01-1-1.txt:67(搜「中间值写在代码行旁边」) |
| 三种记忆总述与对应关系 | 1.2 影响程序设计的不同认知过程 | text/10-ch01-02-1-2.txt:7(搜「无限期存储所有记忆」) · text/10-ch01-02-1-2.txt:11(搜「缺乏知识会影响长时记忆」) |
| 长时记忆存什么 | 1.2 影响程序设计的不同认知过程 | text/10-ch01-02-1-2.txt:21(搜「系鞋带」) · text/10-ch01-02-1-2.txt:23(搜「2147483647」) |
| 短时记忆容量与清空 | 1.2 影响程序设计的不同认知过程 | text/10-ch01-02-1-2.txt:35(搜「不会超过12个」) · text/10-ch01-02-1-2.txt:41(搜「清空短时记忆」) |
| main 错拼成 mian | 1.2 影响程序设计的不同认知过程 | text/10-ch01-02-1-2.txt:56(搜「错拼为」) · text/10-ch01-02-1-2.txt:58(搜「抄近道」) |
| 工作记忆=处理器、追踪 | 1.2 影响程序设计的不同认知过程 | text/10-ch01-02-1-2.txt:64(搜「孕育新想法」) · text/10-ch01-02-1-2.txt:70(搜「脑海里编译并执行代码」) |
| 外部媒介=过载信号 | 1.2 影响程序设计的不同认知过程 | text/10-ch01-02-1-2.txt:74(搜「借助外部媒介」) |
| 错误报告例子 | 1.3 认知过程之间的关系 | text/11-ch01-03-1-3.txt:17(搜「差一错误引起」) |