跳到主要内容

三种记忆,三种困惑 — 代码为什么读不懂的第一层答案

这一章讲三件事: 读代码时大脑内部在动用哪三套装置;「卡壳」为什么至少有三种不同口味; 以及怎么判断自己此刻卡在哪一种。这一章是全书的字典——后面十二章讲的每一个办法, 都是在救这三套装置中的一套。不需要任何基础,遇到的生词都在当场解释。

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。它有两个致命属性:

  • 容量小:大多数科学家认为它一次只装得下十来个东西,而且通常更少7;
  • 用完即弃:当前问题一解决,大脑就清空它,腾地方装下一条信息8

第二段 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=13N2 = 613−6×2 = 1,B$ = "1"N1=13、N2=6、B$="1",还有「下一遍用 N2 当 N1」这条规矩
第 2 遍N1=6N2 = 36−3×2 = 0,B$ = "01"N1=6、N2=3、B$="01"
第 3 遍N1=3N2 = 13−1×2 = 1,B$ = "101"N1=3、N2=1、B$="101"
第 4 遍N1=1N2 = 01−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. 可带走的

  1. 卡壳先分诊:没见过(缺知识)、要查(缺信息)、跟不住(缺加工),三种病三种药;
  2. 长时记忆是唯一能靠积累变富的一套——值得花时间往里存,第 3 章讲怎么存;
  3. 短时记忆又小又快扔——所以「刚才查到什么」转瞬就忘,不是你态度差;
  4. 长时记忆会替你「纠错」,所以你看到的代码不一定是屏幕上的代码——review 时要刻意放慢;
  5. 需要纸笔记中间值 = 工作记忆过载的官方信号,这时候该做的是给代码减负或换工具,不是骂自己;
  6. 别拿「短时记忆能装几个」的具体数字当真理,拿「个位数」这个量级做决策就够。

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 错拼成 mian1.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(搜「差一错误引起」)

Footnotes

  1. 出处:「1.1 代码造成的各种困惑」第 5 段(text/09-ch01-01-1-1.txt:5,搜「3段代码分别采用APL」)。三段代码的作用都是把给定的 N 换算成二进制表示。

  2. 出处:「1.1 代码造成的各种困惑」第 9 段(text/09-ch01-01-1-1.txt:9,搜「困惑之处在于程序员不一定清楚」)。原文指出 APL 例子的困惑在于不清楚运算符 ⊤ 的含义。

  3. 出处:「1.1 代码造成的各种困惑」第 63 段(text/09-ch01-01-1-1.txt:63,搜「缺乏信息」)与第 67 段(text/09-ch01-01-1-1.txt:67,搜「中间值写在代码行旁边」)。前者定义第二种困惑,后者给出第三种困惑的画面:把中间值写在代码行旁边。

  4. 出处:「1.2 影响程序设计的不同认知过程」第 21 段(text/10-ch01-02-1-2.txt:21,搜「系鞋带」)。原文把肌肉反射式的技能和「记住抽象算法」都归入长时记忆。

  5. 出处:「1.2 影响程序设计的不同认知过程」第 23 段(text/10-ch01-02-1-2.txt:23,搜「2147483647」)。

  6. 出处:「1.2 影响程序设计的不同认知过程」第 35 段(text/10-ch01-02-1-2.txt:35,搜「电话里听到对方报出的电话号码」)。

  7. 出处:「1.2 影响程序设计的不同认知过程」第 35 段(text/10-ch01-02-1-2.txt:35,搜「不会超过12个」)。注意第 2 章给出的另一个口径是 2~6 个,见本页第 6 节的说明。

  8. 出处:「1.2 影响程序设计的不同认知过程」第 41 段(text/10-ch01-02-1-2.txt:41,搜「清空短时记忆」)。

  9. 出处:「1.2 影响程序设计的不同认知过程」第 56 段(text/10-ch01-02-1-2.txt:56,搜「错拼为」)与第 58 段(text/10-ch01-02-1-2.txt:58,搜「抄近道」)。原文的原话是大脑「根据长时记忆存储的过往经验决定使用 main,而不是使用实际接收并存储在短时记忆中的 mian」。

  10. 出处:「1.2 影响程序设计的不同认知过程」第 64 段(text/10-ch01-02-1-2.txt:64,搜「孕育新想法」)。

  11. 出处:「1.2 影响程序设计的不同认知过程」第 74 段(text/10-ch01-02-1-2.txt:74,搜「借助外部媒介」)。原文:需要外部媒介存信息,「可能意味着工作记忆的负担过重,难以加工更多信息」。

  12. 出处:「1.1 代码造成的各种困惑」第 67 段(text/09-ch01-01-1-1.txt:67,搜「中间值写在代码行旁边」)。走查表格为本拆解重排;输入值 N=13 是为演示挑选的。

  13. 出处:「1.2 影响程序设计的不同认知过程」第 70 段(text/10-ch01-02-1-2.txt:70,搜「脑海里编译并执行代码」)。原文把「在脑海里编译并执行代码」定义为追踪。