问题解决 — 自动化与样例,两条真正的进阶路
这一章讲三件事: 为什么「多做题、多写代码」的朴素信条被实验打了折扣;小技能练到「不假思索」为什么是大问题的前提; 以及第三个认知负荷——关联认知负荷——怎么解释「加班猛干一晚上,却想不起自己干了啥」。
1. 全景:通用解法的失败逼出两条路
想变强 → 通用解题法?── 科学研究:问题解决不是通用技能,此路不通
→ 那靠什么?── 看强的人强在哪:长时记忆里的存货 + 不占坑位的小技能
├─ 路线一:自动化 —— 把小技能练到不假思索,解题时零负荷
└─ 路线二:样例 —— 精读「解法+讲解」,往长时记忆里存解法
图说:这一章是对全书的回收——读代码的存货观,最终决定你解决问题的水平。
2. 问题解决的实质与「通用法」的破产
一个「问题」由三件东西定义:起点(现状)、终点(目标状态)、规则(什么操作允许做)。给网站加搜索框,起点是现有代码库,终点是「功能上线且不破坏别的测试」,规则是这门语言和框架允许的写法。所有允许的走法合在一起,叫作状态空间(从起点出发,每一步可能的样子)——解题的本质就是用尽量少的步子穿过状态空间,井字棋如此,大系统也如此1。
听起来高手应该有一套通用走法?确实有人总结过:数学家波利亚(Pólya)的《怎样解题》把解题分成理解问题、拟订方案、执行方案三步,风行几十年2。但科学研究一再表明:问题解决不是通用技能——通用法失败的原因都和长时记忆有关3。
书里拿一个回文检测任务(判断一个字符串正读反读是否一样)演示了波利亚三步在哪一步断电:拟订方案时,「反转字符串再比对」这个方案成不成立,取决于当前语言有没有现成的反转函数——Java 有 StringBuilder.reverse(),APL 没有叫 reverse 的东西(它所有关键字都是运算符,反转另有写法),BASIC 干脆没有。同一个人、同一个脑子,换一门语言,「拟订方案」完全不是同一件事4。
更深一层的原因:长时记忆按线索取货,线索越具体,越可能命中。「拟订方案」这个抽象指令给不出具体线索;「尾除法」「减去除数的倍数」这种具体说法才能勾出存货5。加上第 08 章说过的「远迁移很难发生」——「一般解题领域」到「编程」的距离远得几乎迁不动6。
3. 记忆的四分法:高手在「重现」问题
解题靠长时记忆,那记忆里存的到底是什么?认知科学把长时记忆分成几类,编程全用得上7:
- 程序性记忆(内隐的):关于「怎么做」的记忆——盲打、系鞋带、左手条件反射地补右括号;
- 陈述性记忆(外显的):能主动回想的事实——for 循环的语法、某个函数库的调用名;它再分两支:情景记忆(个人经历:「那次熬夜三小时,结果发现错在测试里」)和语义记忆(事实与含义:「5 乘 7 等于 35」)。
研究里最有意思的一条:资深程序员高度依赖情景记忆解题——他们在「重现」熟悉的问题,而不是在「解决」新问题。看到一个层次结构问题,老手想起的是「我当年用树结构解决过类似的」,然后搬出来改一改,不是从零推理8。连「直觉」都有了机制解释:当前问题和存过的某类问题相似时,程序性记忆自动出手,你知道该做什么、却说不出为什么——那是记忆,不是魔法9。
程序性记忆也有账单:它学得越深越改不动。练熟 QWERTY 键盘的人再学新键盘布局,比新手难得多;作者自己从 C# 转 Python 多年,写循环时手指还是时不时自己敲出 foreach10。
4. 路线一:自动化——把小技能练到零负荷
自动化就是反复练习到某项技能不假思索:走路、盲打、解一元二次方程。书里给它的比喻值得直接引用:像游戏里解锁二段跳——有些地方,不解锁就是到不了11。
为什么它决定解题上限?回到坑位账:工作记忆一次五六个坑。如果 for 循环语法、下标边界这些小件各自占着坑,留给「这个问题本身」的坑就所剩无几;小技能全部自动化后,它们零坑位入场,五个坑全数让给难题。自动化就是第 04 章「减外部负荷」的终极形态——负荷不是被外部工具卸掉的,是技能自己长没了。
自动化有三段路12:
| 阶段 | 表现 | 自查问题 |
|---|---|---|
| 认知阶段 | 每一步都要有意识地想,图式在这里新建 | 「我是不是要全神贯注才做得了?」 |
| 联想阶段 | 靠重复形成反应套路,有效的记住、无效的淘汰 | 「我是不是靠小技巧撑着?」 |
| 自主阶段 | 任何上下文里都不用想,一眼即中 | 「我能不能边做边想别的?」 |
它的机制,洛根(Logan)的实例理论说得很直白:每次做题都存进情景记忆一条「实例」;类似的题再来,大脑直接取旧实例照着做,不再推理——自动化完成的标志,就是取记忆完全取代了推理;而取记忆天然比推理快13。
怎么练?书里两条对程序员意外陌生的建议14:写大量相似但不同的程序——想焊死 for 循环,就正着遍历、倒着遍历、变步长遍历各写一打;改程序而非从零写——拿「循环版」逐行改成「列表推导式版」,差异对比正是长时记忆最爱的进料方式。再配上第 03 章的间隔重复:每天几分钟,练到不费吹灰之力为止。
5. 路线二:样例——5 倍的实验
第二条路更反直觉:认真研究别人的解法,比埋头自己解更有效。
提出认知负荷理论的斯威勒(Sweller)本来是数学老师,他发现学生解了一年代数题几乎什么都没学会,由此转向研究怎么学才有效。他和库珀(Cooper)在 80 年代找了 20 位九年级学生(14–15 岁)做实验:两组解同样的代数方程,一组自力更生,另一组每道题配一份样例——完整解题步骤外加口诀式讲解15。
结果是本书最响的一个数字:看样例那组,解题速度是自力更生组的 5 倍16。
怀疑者(包括很多老师)会说:照着口诀解,学生只是机械模仿、不懂原理。实验设计里专门排了这个坑——后续换一批没见过的方程再考,样例组依然领先;他们从样例里提炼出的「移项变号」「两边同除」是可迁移的一般方法,不是口诀复读17。这个「样例效应」后来在不同年龄、不同学科(数学、音乐、国际象棋、体育、编程)的研究里反复重现18。
解释它的正是第三种认知负荷——关联认知负荷:大脑把信息转成长时记忆(建图式、做自动化)所付出的那部分努力。坑位总量恒定:内部、外部负荷一占满,留给「学」的坑位就是零——这就是为什么自力更生组辛苦一下午,长时记忆里却没存下什么19。样例组因为照着步骤走,负担轻,坑位有富余,反而把「移项变号」这类一般规律存了进去。生活版注脚:加班「撸代码」到深夜的人,第二天常想不起自己写过什么——内部外部负荷双双爆表,关联负荷颗粒无收20。
对「多写代码就是变强」的信条,书里引了克尔希纳(Kirschner)的一句话作结:**「一个人不会因为做了专业的事情就成为专家。」**编程实验的结论同方向:对初学者,读带讲解的代码比多写代码收获更大21。
6. 主走查:同一个程序员的两周进级计划
把两条路线落到一个人身上。设定:一位中级程序员,痛点是写循环类代码慢、见新算法没思路(人物与日程为本拆解虚构,用于演示两条路线怎么排;引用的练习形式全部来自原书)22:
第 1 步:体检(对应 10.3 的练习)
新开一个会话,写下今天用到的每项小技能,标它处在哪个阶段:
for 循环变体 —— 自主?不,倒序遍历要想两秒 → 联想阶段
双层循环边界 —— 每次都要心算 → 认知阶段
列表推导式 —— 认得但写不出 → 联想阶段
第 2 步:路线一(自动化),每天 15 分钟
对「认知/联想阶段」的技能,写「相似但不同」的小程序:
第 1~3 天:正序、倒序、跳步遍历各 5 个变体
第 4~6 天:把 6 段循环版代码逐行改写成列表推导式
判据:10.3 练习的原话——「能否轻松完成任务的同时还有余力想别的」
第 3 步: 路线二(样例),每天 20 分钟
读一份「带解释的解法」:书里推荐从 GitHub 上挑自己用过的库读,
或参加代码阅读俱乐部;读完按第 06 章的七步写摘要
关键:读「解法+为什么这么解」的配对,不是裸读源码——
样例效应的活性成分是步骤加讲解,不是代码本身
第 4 步:两周后复盘
重做第 1 步的体检表:哪些技能挪进了自主阶段?
算法没思路的问题:情景记忆里现在存着几个「见过的新解法」?
走查里两条路线的分工点一下名:第 2 步在腾坑位(自动化让小件零坑入场),第 3 步在进存货(样例把解法写进长时记忆)——正是本章开头那句话的两半。
7. 作者的判断与证据
**有实验撑着的:**问题解决非通用技能(认知科学多项研究);样例效应 5 倍(Sweller & Cooper,20 人,九年级)及其跨学科重复;实例理论(Logan);自动化三阶段(认知心理学的经典分段);情景记忆主导老手解题。
作者的发挥,要分开看:「二段跳」比喻是作者的;「每天 15/20 分钟」式的具体处方是本拆解为演示排出来的,书里的建议只到「留出时间、坚持间隔重复」的粒度;把样例效应直接推到资深工程师身上(实验对象是中学生)属于合理但未验证的延伸。
书的坦白:「许多专业程序员可能对 Sweller 的研究结果感到惊讶」——作者明知这条与行业直觉冲突,选择站在数据一边,并专门写了「不必担心口诀教学」一节回应质疑。
8. 边界与局限
- 样例效应的证据链在初学者身上最扎实;专家级学习者的样例收益研究少,「读代码对老手同样最高效」没有同等的实验地位;
- Sweller & Cooper 样本 20 人、单校九年级——5 倍是方向性的数,不是可复用的精确值;样例效应跨学科可重复,但倍数各研究不一;
- 自动化的暗面在本章自己就出现了:
foreach的手指记忆证明练错的技能同样会自动化——练什么比练多久更要紧; - 关联认知负荷目前是解释性理论(用来说明现象的框架),不是可直接测量的量——第 10 章那些测量手段测的是总负荷,分不出三种成分。
9. 可带走的
- 遇到难题先问「这题像不像我做过的」——检索情景记忆比从头推理快,这是正当解法不是作弊;
- 给自己的小技能分级(认知/联想/自主), 把处于认知阶段的列成清单——那是最值得练的;
- 练习要「相似但不同」:一个技能的一打变体,好过十个技能各摸一遍;
- 学新语法时做「改写练习」:把旧写法逐行改成新写法,差异即记忆;
- 学新领域先读带讲解的解法,再自己写——「先看样例」不丢人,它是被 5 倍实验支持的路;
- 给别人的代码写讲解(摘要),你产出的就是别人的样例——这是团队里最便宜的传帮带;
- 「多写代码」要改成「多写有反馈的代码」:没有存货沉淀的重复,只是消耗坑位;
- 深夜赶工产出的东西,第二天自己都不认识——那不是你变了,是关联认知负荷当时为零。
10. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 三要素与状态空间 | 10.1 问题解决的实质 | text/59-ch10-01-10-1.txt:9(搜「三大要素」) · text/59-ch10-01-10-1.txt:21(搜「状态空间」) |
| 波利亚三阶段 | 10.2 长时记忆在解决编程问题时所起的作用 | text/60-ch10-02-10-2.txt:9(搜「George Pólya」) · text/60-ch10-02-10-2.txt:11(搜「理解问题」) |
| 不是通用技能 | 10.2 长时记忆在解决编程问题时所起的作用 | text/60-ch10-02-10-2.txt:17(搜「通用技能」) |
| 回文三语言 | 10.2 长时记忆在解决编程问题时所起的作用 | text/60-ch10-02-10-2.txt:21(搜「是否为回文」) · text/60-ch10-02-10-2.txt:31(搜「StringBuilder.reverse()」) · text/60-ch10-02-10-2.txt:35(搜「都是运算符」) |
| 线索具体度 | 10.2 长时记忆在解决编程问题时所起的作用 | text/60-ch10-02-10-2.txt:41(搜「线索越具体」) |
| 记忆四分法 | 10.2 长时记忆在解决编程问题时所起的作用 | text/60-ch10-02-10-2.txt:57(搜「程序性记忆」) · text/60-ch10-02-10-2.txt:61(搜「情景记忆」) · text/60-ch10-02-10-2.txt:65(搜「语义记忆」) |
| 重现而非解决、直觉 | 10.2 长时记忆在解决编程问题时所起的作用 | text/60-ch10-02-10-2.txt:73(搜「重现而非解决」) · text/60-ch10-02-10-2.txt:75(搜「直觉」) |
| 忘却的账单 | 10.2 长时记忆在解决编程问题时所起的作用 | text/60-ch10-02-10-2.txt:81(搜「德沃夏克」) · text/60-ch10-02-10-2.txt:83(搜「foreach」) |
| 二段跳与三阶段 | 10.3 自动化:构建内隐记忆 | text/61-ch10-03-10-3.txt:7(搜「二段跳」) · text/61-ch10-03-10-3.txt:21(搜「3个阶段」) · text/61-ch10-03-10-3.txt:27(搜「认知阶段」) · text/61-ch10-03-10-3.txt:39(搜「自主阶段」) |
| Logan 实例理论 | 10.3 自动化:构建内隐记忆 | text/61-ch10-03-10-3.txt:51(搜「实例理论」) · text/61-ch10-03-10-3.txt:53(搜「完全依靠情景记忆」) |
| 刻意练习两法 | 10.3 自动化:构建内隐记忆 | text/61-ch10-03-10-3.txt:77(搜「相似但不同」) · text/61-ch10-03-10-3.txt:79(搜「修改程序」) · text/61-ch10-03-10-3.txt:81(搜「间隔重复」) |
| 样例实验 ★ | 10.4 从代码及其解释中汲取经 验 | text/62-ch10-04-10-4.txt:11(搜「20位九年级学生」) · text/62-ch10-04-10-4.txt:17(搜「5倍」) |
| 迁移与跨学科 | 10.4 从代码及其解释中汲取经验 | text/62-ch10-04-10-4.txt:19(搜「依然优于」) · text/62-ch10-04-10-4.txt:21(搜「得到重复」) |
| 关联认知负荷 | 10.4 从代码及其解释中汲取经验 | text/62-ch10-04-10-4.txt:33(搜「关联认知负荷」) · text/62-ch10-04-10-4.txt:35(搜「图式构建」) · text/62-ch10-04-10-4.txt:37(搜「加班」) |
| Kirschner 与编程版 | 10.4 从代码及其解释中汲取经验 | text/62-ch10-04-10-4.txt:45(搜「成为专家」) |
| 落地资源 | 10.4 从代码及其解释中汲取经验 | text/62-ch10-04-10-4.txt:53(搜「Code Reading Club」) · text/62-ch10-04-10-4.txt:59(搜「GitHub」) · text/62-ch10-04-10-4.txt:63(搜「500 Lines or Less」) |