AI OUTLINE · BEFORE READING
《程序员超强大脑》AI 大纲
全书视角AI 生成含原文摘录
本大纲由 AI 读书(阅读端智能大纲)生成,是全书结构与要点的机器梳理, 不等同于已亲读;其中第 4 层条目为书中原句摘录。章节可展开查看。
章节目录
- 本书讨论的3种认知过程一览
- 关于本书
- 第一部分 代码阅读
- 第1章 剖析程序设计之惑
- 1.1 代码造成的各种困惑
- 1.2 影响程序设计的不同认知过程
- 1.3 认知过程之间的关系
- 1.4 小结
- 第2章 快速阅读代码
- 2.1 快速阅读代码
- 2.2 弥补记忆容量不足的短板
- 2.3 看到的代码比读到的代码多
- 2.4 小结
- 第3章 快速学习编程语法
- 3.1 语法记忆小贴士
- 3.2 如何利用抽认卡快速学习语法
- 3.3 如何避免遗忘
- 3.4 如何牢记编程语法
- 3.5 小结
- 第4章 阅读复杂的代码
- 4.1 为什么复杂的代码难以理解
- 4.2 减轻认知负荷的方法
- 4.3 利用记忆辅助工具解决工作记忆过载的问题
- 4.4 小结
- 第二部分 代码思考
- 第5章 深入理解代码
- 5.1 变量角色框架
- 5.2 角色和范式
- 5.3 加深对程序的了解
- 5.4 阅读代码和阅读文本有相似之处
- 5.5 运用文本理解策略来阅读代码
- 5.6 小结
- 第6章 更好地解决编程问题
- 6.1 借助模型来思考代码
- 6.2 心智模型
- 6.3 概念机器
- 6.4 概念机器和语言描述
- 6.5 概念机器和图式
- 6.6 小结
- 第7章 迷思概念:错误的思维方式
- 7.1 为什么学习第二门编程语言比学习第一门编程语言更容易
- 7.2 迷思概念:思维中存在的错误
- 7.3 小结
- 第三部分 代码编写
- 第8章 提高命名的质量
- 8.1 命名为什么重要
- 8.2 从认知的角度剖析命名
- 8.3 哪些类型的标识符更容易理解
- 8.4 标识符与代码错误之间的关系
- 8.5 如何设计质量更高的标识符
- 8.6 小结
- 第9章 避免低质量代码和认知负荷:两种框架
- 9.1 为什么存在异味的代码会加重认知负荷
- 9.2 低质量标识符对认知负荷的影响
- 9.3 小结
- 第10章 提高解决复杂问题的能力
- 10.1 问题解决的实质
- 10.2 长时记忆在解决编程问题时所起的作用
- 10.3 自动化:构建内隐记忆
- 10.4 从代码及其解释中汲取经验
- 10.5 小结
- 第四部分 代码协作
- 第11章 编程活动和任务
- 11.1 程序设计包括不同的编程活动
- 11.2 受到干扰的程序员
- 11.3 小结
- 第12章 设计和改进大型系统
- 12.1 代码库的属性
- 12.2 认知维度和编程活动
- 12.3 小结
- 第13章 对新程序员进行适岗培训
- 13.1 适岗培训中存在的问题
- 13.2 高级程序员与初级程序员的区别
- 13.3 完善适岗培训流程
- 13.4 小结
- 写在最后
本书讨论的3种认知过程一览
本书讨论的3种认知过程
- 短时记忆、长时记忆和工作记忆是本书讨论的3种认知过程
- 箭头1代表进入大脑的信息,箭头2代表进入短时记忆的信息,箭头3代表从短时记忆转移到工作记忆的信息,这些信息与来自长时记忆的信息(箭头4)相结合。
- 工作记忆是大脑在进行思维活动时加工信息的机制。
关于本书
本书的目标读者和阅读建议
- 适合各个层次的程序员阅读,希望深入了解大脑的工作机制,提高自己的编程技巧并改善编程习惯。
- 应该具备在开发团队中工作的经验或开发大型软件系统的经验,并熟悉如何培训团队新人
- 书中经常会涉及这类场景,阅读时结合自身经验可以加深理解。
- 如果能做到融会贯通,把新的信息与现有的知识和经验联系起来,则有助于提高学习能力。
本书的内容设置和阅读建议
- 本书分为四大部分,一共13章,建议按章节顺序阅读
- 每章都包括若干应用场景和练习,以帮助读者消化概念并加深理解。
- 日常实践也是学以致用的机会,建议把本书作为案头常备书,在编程实践中运用每一章所学的知识,再继续阅读其他章节。
本书的代码示例和注释
- 本书包括众多源代码示例,有些以“代码清单×-×”的形式出现,有些则直接嵌入正文
- 无论哪种代码,本书都使用特殊的格式将其与正文区分开。
- 代码有时也会以粗体显示,以突出与之前的代码有哪些不同。
- 书中的代码注释主要用于解释和说明重要的内容。
本书的论坛和作者参与
- 购买了本书英文版的读者可以免费访问Manning出版社运营的专享论坛
- 你可以在论坛上发表对图书的评论、提出技术问题并得到作者或其他用户的帮助。
- Manning出版社承诺为读者提供一个平台,供读者之间以及读者和作者之间进行有意义的交流,但无法保证作者的具体参与程度。
第一部分 代码阅读
阅读代码的难度原因
- 专业程序员不一定清楚如何阅读代码
- 他们很少有机会学习或实践这项技能。
- 研究令人一头雾水的代码往往很辛苦。
第1章 剖析程序设计之惑
程序设计中的困惑及其原因
- 困惑在程序设计中司空见惯,可能由多种原因造成
- 学习新的编程语言、概念或框架时,接触到的各种新想法可能会吓到程序员。
- 阅读陌生的代码或自己很久以前所写的代码时,程序员也许不明白代码的作用或忘记了当时这样编写代码的原因。
- 每次接触新的业务领域时,新的术语和行话都会在程序员的脑海里相互碰撞。
- 困惑的形式五花八门,不同类型的困惑与不同类型的认知过程有关
- 不知道领域概念的含义可能困扰程序员,逐字逐句阅读复杂的算法同样会给他们带来困惑。
程序设计中的三种认知过程
- 通过各种代码示例详细介绍3种困惑,并解释大脑的活动情况
- 认知过程1:模式识别。
- 认知过程2:问题解决。
- 认知过程3:抽象思维。
如何改善程序设计中的认知过程
- 读完本章后,你将能区分代码可能造成的各种困惑,并理解大脑内部相应的认知过程
- 在介绍3种困惑以及3种相关的认知过程后,后续章节将讨论如何改善这些认知过程。
1.1 代码造成的各种困惑
代码造成的困惑类型
- 第一种困惑:缺乏知识
- 困惑之处在于程序员可能不清楚T的含义。
- 第二种困惑:缺乏信息
- 困惑之处在于很难判断toBinaryString()方法的具体用途,需要从其他代码中寻找线索。
- 第三种困惑:缺乏加工能力
- 困惑的原因在于大脑无法记住执行过程的所有步骤。
认知过程与困惑的对应关系
- 第一种困惑对应于理解代码所需的特定领域的知识。
- 第二种困惑对应于理解代码所需的信息检索能力。
- 第三种困惑对应于大脑处理复杂信息的能力。
1.2 影响程序设计的不同认知过程
影响程序设计的不同认知过程
- 缺乏知识会影响长时记忆
- 长时记忆可以无限期存储所有记忆,缺乏知识意味着长时记忆中缺少足够的相关事实。
- 缺乏信息会影响短时记忆
- 大脑获取的信息暂时存储于短时记忆,但如果需要检索的内容过多,那么大脑可能会遗忘部分已经获取的信息。
- 缺乏加工能力会影响工作记忆
- 思维活动在工作记忆中进行,当大脑必须加工大量信息时会受到影响。
长时记忆和程序设计
- 长时记忆可以把记忆保存很长时间
- 无论是系鞋带还是编写二分搜索程序,人类的所有活动都与长时记忆有关。
- 长时记忆会存储几类相关的编程信息
- 例如成功运用某种编程技巧的记忆、Java关键字的含义、英语单词的含义或者Java中int型变量的最大值(2147483647)。
短时记忆和程序设计
- 短时记忆用于暂时保存大脑接收的信息
- 当我们在电话里听到对方报出的电话号码时,号码不会直接进入长时记忆,而是首先进入容量有限的短时记忆。
- 短时记忆的容量有限,通常不会超过12个
- 在阅读程序时,大脑会把程序使用的关键字、变量名和数据结构暂时保存在短时记忆中。
工作记忆和程序设计
- 工作记忆是孕育新想法、新观点以及新方案的摇篮
- 如果把长时记忆比作硬盘,短时记忆比作内存,那么工作记忆就相当于大脑的“处理器”。
- 在阅读代码的过程中,大脑还会进行更多活动,如追踪
- 程序员会尝试心算并梳理代码的执行结果,这个过程称为追踪,也就是在脑海里编译并执行代码。
1.3 认知过程之间的关系
认知过程之间的关系
- 长时记忆、短时记忆和工作记忆之间的关系密切
- 任何思考活动其实都会在某种程度上激活所有3种认知过程。
简要剖析认知过程如何相互作用
- 任何思考活动其实都会在某种程度上激活所有3种认知过程
- 阅读Java代码片段时,大脑把“n是整数”这类信息存储在短时记忆中,同时从长时记忆中提取出整数的定义,并利用工作记忆思考这段代码的作用。
编程任务涉及的认知过程
- 程序员阅读客户提交的错误报告时,所有信息都会进入工作记忆,大脑利用工作记忆来思考如何解决当前的问题
- 问题似乎由差一错误引起。错误报告通过感官进入大脑,为解决问题,程序员必须再次阅读几个月前所写的代码。
- 在重读代码的过程中,大脑把接收到的信息保存在短时记忆中,并从长时记忆中检索几个月前的信息(例如当时使用的Actor模型)。
- 除了有关经验的记忆,长时记忆还会存储事实信息(例如差一错误的解决方案)。
练习1-1:阅读代码并确定它们的作用,同时认真想一想阅读代码时涉及哪些认知过程
- 阅读代码片段1 APL程序,确定其作用,并分析阅读时涉及的认知过程
- 代码片段1 APL程序的作用是计算输入的数字字符串中所有奇数位和偶数位之和,并判断其和是否能被10整除。
- 阅读代码片段1 APL程序时,涉及的认知过程包括短时记忆、长时记忆和工作记忆。
- 阅读代码片段2 Java程序,确定其作用,并分析阅读时涉及的认知过程
- 代码片段2 Java程序的作用是判断输入的数字字符串是否符合Luhn算法,即所有奇数位和偶数位之和是否能被10整除。
- 阅读代码片段2 Java程序时,涉及的认知过程包括短时记忆、长时记忆和工作记忆。
- 阅读代码片段3 BASIC程序,确定其作用,并分析阅读时涉及的认知过程
- 代码片段3 BASIC程序的作用是将输入的字符串反转并输出。
- 阅读代码片段3 BASIC程序时,涉及的认知过程包括短时记忆、长时记忆和工作记忆。
1.4 小结
程序员在编程过程中可能遇到的困惑
- 缺乏知识、缺乏容易获取的信息或大脑缺乏加工能力都可能令程序员感到困惑。
代码阅读与编写的认知过程
- 阅读或编写代码时涉及3种认知过程
- 第一种认知过程:大脑从长时记忆中提取信息(例如关键字的含义)。
- 第二种认知过程:大脑把当前的程序信息(例如方法名和变量名)暂时存储在短时记忆中。
- 第三种认知过程:大脑在工作记忆中加工代码的相关信息(例如发现索引少1)。
代码阅读中的三种认知过程相辅相成
- 遇到变量名时,大脑将其存储在短时记忆中,并从长时记忆中检索曾经读过的相关程序。
- 遇到模棱两可的单词时,大脑会激活工作记忆,并努力根据上下文确定这个单词的确切含义。
第2章 快速阅读代码
剖析资深程序员也很难快速阅读代码的原因
- 代码阅读时获得的信息会首先进入短时记忆,大脑很难加工代码包含的大量信息
- 如果程序员了解快速阅读代码时大脑的活动情况,则更容易判断自己的理解正确与否。
分析大脑如何将新信息分解为可识别的元素
- 长时记忆负责存储记忆和事实,短时记忆负责暂时存储进入大脑的信息,工作记忆负责加工长时记忆和短时记忆存储的信息以进行思维活动
- 长时记忆相当于计算机硬盘,短时记忆相当于计算机内存,工作记忆相当于计算机处理器。
探讨长时记忆和短时记忆协作分析信息(例如单词或代码)的方式
- 通过练习快速浏览多个代码片段等方法来提高代码阅读水平
- 在保证准确的前提下加快代码阅读速度,对于提高编程水平大有裨益。
介绍图像记忆在加工代码时所起的作用
- 图像记忆在加工代码时,可以将代码转换为更容易理解和识别的图像
- 通过图像记忆,程序员可以更快地理解和分析代码。
解释如何通过记忆代码来(自我)评估编程水平
- 记忆代码可以帮助程序员更好地理解和分析代码,从而评估自己的编程水平
- 通过记忆代码,程序员可以更好地理解代码的结构和逻辑,从而提高自己的编程水平。
练习编写更便于其他人阅读的代码
- 编写更便于其他人阅读的代码,可以提高代码的可读性和可维护性
- 通过编写易于理解的代码,程序员可以帮助其他人更快地理解和分析代码,从而提高团队的协作效率。
2.1 快速阅读代码
代码阅读的重要性
- 代码是写给人看的,只是恰好能让机器运行
- 程序员练习写代码的时间其实远远超过练习读代码的时间。
- 阅读代码的目的都是查找代码包含的特定信息
- 提高快速查找相关信息的能力有助于减少反复浏览代码的次数。
大脑的活动情况
- 在默写实现插入排序的Java程序时,大脑会同时使用短时记忆和长时记忆
- 短时记忆用于存储大脑刚刚接收到的一些信息。
- 长时记忆则通过两种方式加以巩固,一种是依靠Java的语法知识,另一种是依靠“程序功能是实现插入排序”这一事实。
回顾默写的代码
- 默写代码时,大脑会把短时记忆和长时记忆结合起来使用
- 从长时记忆中能提取出哪些信息与长时记忆存储的信息有关。
- 程序员有时会在默写代码时添加注释以帮助记忆。
回顾第二次默写的代码
- 记住第二个程序要费点儿脑筋,因为你不清楚这段代码的确切作用,而且不熟悉的标识符会增加快速查找、识别和记忆模式的难度
- 你不清楚这段代码的确切作用,所以很难依靠长时记忆存储的知识来填补记忆空白。
- 我有意选择“奇怪”的变量名(例如b和l)作为循环迭代器。不熟悉的标识符会增加快速查找、识别和记忆模式的难度。
阅读不熟悉的代码时为什么会感到困难
- 默写读过的代码并非易事,根本原因在于短时记忆的容量有限
- 阅读代码时,大脑无法把代码包含的所有信息都存储在短时记忆中以便加工。
- 信息在短时记忆中的留存时间不超过30秒,30秒后必须进入长时记忆,否则将永远丢失。
- 由于短时记忆的容量有限,因此大脑会结合使用短时记忆和长时记忆来理解正在阅读或记忆的内容。
2.2 弥补记忆容量不足的短板
短时记忆的容量限制及其在阅读代码时的应用
- 短时记忆最多只能存储6个信息元素,但也能够记住并加工超过6个字母的信息
- “短时记忆最多只能存储6个信息元素”不只适用于代码阅读,也适用于所有认知任务。
- 为剖析阅读陌生代码为何如此之难,接下来我们讨论科学家针对国际象棋棋手所做的重要实验
- 实验结果揭示出了短时记忆的更多奥秘。
组块威力大
- 职业棋手比普通棋手更善于还原棋局,是因为职业棋手的短时记忆容量更大
- 职业棋手能够记住更多棋局,从而在对弈时展现出高人一筹的实力。
- 普通棋手在两项实验中主要采用逐子记忆的方式,而职业棋手则严重依赖长时记忆存储的信息
- 普通棋手试图记住每一枚棋子的位置,导致短时记忆的容量很快耗尽。
- 职业棋手可能这样记忆棋局:“西西里防御开局,但马的位置左移两格。”。
- 一些职业棋手还会结合自己的对弈经历或曾经接触过的对局来记忆棋局。
- 组块理论也能很好地解释为什么两组棋手在第二项实验中的表现差别不大
- 面对随意摆放的棋子,职业棋手无法再依靠长时记忆存储的棋局“数据库”迅速对棋子进行分组。
资深程序员比新手程序员更善于记忆代码
- 高级程序员的表现优于中级程序员,中级程序员的表现又优于初级程序员
- 在记忆没有打乱顺序的程序时,高级程序员的表现优于中级程序员,中级程序员的表现又优于初级程序员。
- 在记忆打乱顺序的程序时,3组程序员的表现几乎没有区别。
- 编程新手能够加工的代码比编程老手要少得多
- 无论对团队新人进行适岗培训还是学习一门新的编程语言,请牢记这一点。
2.3 看到的代码比读到的代码多
感觉记忆和图像记忆
- 感觉记忆用于暂时存储大脑接收的视觉、听觉或触觉信息
- 形、声、闻、味、触这五感在感觉记忆中均有对应的“缓存”。
- 图像记忆是感觉记忆中的一种,用于暂时存储视觉刺激
- 眼睛刚刚看到的图像会产生视觉刺激,而图像记忆可以暂时存储视觉刺激。
图像记忆在阅读代码时的作用
- 眼睛看到的任何信息首先进入图像记忆,但短时记忆并不会加工图像记忆存储的所有信息
- 当阅读代码产生的大量信息进入大脑时,大脑必须在加工信息时做出取舍。
- 利用图像记忆来提高代码阅读的效率:首先扫一眼代码,然后回忆看到的内容
- 练习“扫视代码”有助于大脑建立起对代码的初步印象。
记忆的方法对理解代码的影响
- 记忆的顺序能帮助程序员理解代码
- 初级程序员和高级程序员采用不同的方式划分ALGOL关键字。
- 编写可分块代码、使用设计模式、编写注释、添加信标等技巧有助于提高代码的可读性和可记忆性
- 完成前文给出的所有“记忆-分块”练习后,你就会开始对哪类代码可以分块形成条件反射。
- 使用设计模式有助于编写容易分块的代码。
- 程序员应该负责编写注释,还是追求“代码即文档”(自文档化)?
- 添加信标(beacon)也能使代码分块过程变得更容易。
代码分块练习
- 通过刻意练习来提高代码分块能力
- 如果希望刻意练习代码分块,那么积极尝试记忆代码是一种很好的手段。
- 默写代码是一种评估代码阅读记忆水平的方法
- 测试的出发点是大脑更容易记住熟悉的概念。
2.4 小结
短时记忆的特点
- 短时记忆能存储2~6个信息元素。
长时记忆与短时记忆的协作
- 为弥补容量不足的短板,短时记忆和长时记忆会协作记忆信息。
大脑的信息处理方式
- 大脑会设法把接收到的新信息分解为可识别的元素(称为组块)。
长时记忆对阅读代码的影响
- 如果长时记忆没有存储足够的知识,那么大脑就不得不依靠字母和关键字等低层次的信息元素来阅读代码,从而很快耗尽短时记忆的容量。
- 如果长时记忆存储的相关信息足够多,那么大脑就能记住抽象的概念(例如“Java的for循环”或“Python的选择排序”)而不是低层次的信息元素,从而减少短时记忆的占用。
阅读代码时信息的处理过程
- 阅读代码时获得的信息首先进入图像记忆,只有少部分信息随后进入短时记忆。
利用记忆代码评估编程知识
- 大脑最容易记住已经知道的信息,因此在阅读代码时,能够记住的那些内容揭示出了程序员最熟悉的设计模式、编程结构或领域概念。
代码特征的易加工性
- 设计模式、注释、显式信标等特征更便于大脑加工代码。
第3章 快速学习编程语法
熟记大量语法知识的重要性
- 熟记编程语法、概念和数据结构可以在一定程度上加快代码加工速度
- 已经知道的信息会影响大脑加工代码的效率。
选择记忆编程语法的方法
- 本章将介绍4种重要的方法,以帮助程序员更快、更扎实地记忆编程概念
- 这些方法可以强化长时记忆存储的编程概念,从而提高代码阅读和代码分块的效率。
梳理防止遗忘语法的手段
- 运用精细加工策略巩固并增强记忆编程概念的效果
- 精细加工策略是一种将新信息与已有知识关联起来,从而增强记忆的策略。
分析何时学习语法和编程概念可以获得最佳效果
- 探讨长时记忆存储语法和编程概念的方式。
程序员超强大脑
- 程序员需要熟记编程语法、概念和数据结构
- 程序员知道System.out.print()是用于输出的Java方法,但不一定记得住所有Java方法。
- 从第2章的讨论可知,已经知道的信息会影响大脑加工代码的效率,因此熟记编程语法、概念和数据结构可以在一定程度上加快代码加工速度。
3.1 语法记忆小贴士
记忆语法的重要性
- 大脑已经知道的信息会在很大程度上影响阅读代码和理解代码的效率
- 程序员掌握的概念、数据结构或语法越多,对代码分块就越容易,记忆和加工代码的效率因而越高。
- 思路被打断所造成的影响或许远超想象
- 程序员可能因为沉迷于阅读编程网站和论坛的详细讨论而忘记时间。
- 受到干扰的程序员往往需要大约一刻钟才能重新开始编写代码。
- 如果在程序员修改某个方法时打断他们,那么只有1/10的程序员能够在1分钟内找回工作状态。
- 一旦程序员将注意力从正在编写的代码转向其他事情,就很容易忘记代码的重要信息。
- 从搜索引擎切换回IDE时,他们可能要想一想“刚才写到哪里了?”。
- 程序员往往要花大力气来重建上下文,例如浏览代码库的几个位置来回忆细节,然后才能继续编写代码。
3.2 如何利用抽认卡快速学习语法
抽认卡的制作与使用
- 制作抽认卡时既可以使用纸质卡片,也可以使用便利贴
- 卡片一面写有文字提示(需要学习的内容),另一面写有相应的知识。
- 使用抽认卡时,首先阅读卡片一面的文字提示,尽可能回忆相应的语法并写在另一张纸上,然后对照卡片另一面的代码,看看自己的回答是否正确
- 抽认卡通常用于学习第二门语言且效果极佳。
- 即使是C++这类大型编程语言的规模也远远小于任何自然语言,所以利用抽认卡学习一门编程语言的大部分基本语法元素相对来说不难做到。
抽认卡的应用技巧
- 利用抽认卡勤加练习,Cerego、Anki、Quizlet等不少应用程序也支持创建自定义数字抽认卡
- 这些应用程序可以提醒用户何时再次练习,是学习语法的有力工具。
- 如果程序员经常使用纸质抽认卡或数字抽认卡进行练习,那么语法词汇量在几周后就会出现明显增长,从而既能节省大量搜索时间,也能集中注意力,同时还能提高代码分块水平。
- 扩充抽认卡的机会有很多,如在学习新的编程语言、框架或库时,为每次遇到的新概念制作一张抽认卡
- 如果需要上网搜索某个概念,则表明你对这个概念尚未烂熟于心,这显然是应该扩充抽认卡的另一个信号。
- 请把决定搜索的概念写在卡片的一面,把找到的代码写在另一面。
- 精简抽认卡,如果程序员经常使用抽认卡,那么一段时间后可能已经把某些卡片倒背如流,这时候也许应该考虑抽认卡的“瘦身”问题了
- 统计一下回答正确和回答错误的次数并记在卡片上,以评估自己对某个概念的熟悉程度。
- 如果连续多次答对某个概念,那么可以把相应的抽认卡抽出来。当然,如果后来发现仍然不太熟悉这个概念,则随时可以把卡片放回去。
3.3 如何避免遗忘
遗忘的形式和原因
- 长时记忆的衰退期不像短时记忆那样以秒为单位,但仍然比我们想象的短得多
- 大脑在1小时后通常已经忘记一半信息,两天后能够记住的信息只剩25%。
- 长时记忆只有经过反复练习才能实现信息的长期留存
- 读到、听到或看到的信息会从短时记忆进入长时记忆,但并不意味着长时记忆可以永久保存这些信息。
遗忘的原因何在
- 大脑不会把记忆转换为二进制形式进行保存,而是按网络结构来组织的
- 之所以采用网络结构,是因为事实之间的联系非常紧密。
- 艾宾浩斯发现,大脑在存储记忆的同时也会存储联想或记忆之间的关系
- 如果程序员需要记忆列表推导式的语法,那么了解for循环的语法可能有所帮助。
间隔重复
- 艾宾浩斯发现,自己可以在第一天复习68次、第二天复习7次(总共复习75次)的情况下熟练记住一组12个无意义音节
- 他借助节拍器大声朗读这些无意义音节,并记下需要练习多长时间才能完全记住所有选定的无意义音节。
- Bahrick一家定下学习300个外语单词的目标,复习每组单词的间隔时间不同
- 对于学习次数最多、间隔时间最长的那组单词(每隔8周学习26次),他们记得最清楚,可以回想起76%的内容。
- 对于每隔2周学习26次的那组单词,他们只能回想起56%的内容。
- 学习的时间跨度越长,记忆的留存率越高
- 这并不代表需要增加学习时间,而是应该延长学习周期。
- 长期来看,一个月复习一次抽认卡足以巩固记忆,这也是相对可行的一种策略。
3.4 如何牢记编程语法
记忆信息的两种机制
- 存储强度表示信息在长时记忆中的存储情况
- 某种知识学得越多,记得就越牢,直到留下不可磨灭的印象。
- 提取强度表示记忆信息的难易程度
- 相信你一定有过这样的经历:知道自己肯定记得某些信息(例如姓名、歌曲、电话号码或JavaScript中filter()函数的语法),但就是想不起来,或是话到嘴边却说不出来。
“眼见”还不够
- 问题不在于知识在长时记忆中的存储情况(存储强度),而在于查找这些知识的难易程度(提取强度)
- 即使曾经多次读过代码,但如果只是一扫而过,也未必能留下深刻印象。
主动回忆能够巩固记忆
- 经常练习能使信息提取变得更容易
- 如果程序员从来没有下功夫去记忆语法,则很难在需要时想起它们。
- 主动回忆学过的知识反而可以记住更多内容
- Ballard的实验表明,在没有额外学习的情况下,主动回忆学过的知识反而可以记住更多内容。
主动思考也能巩固记忆
- 提取练习可以增加信息的留存时间,而且练习的时间跨度越大,记忆信息的效果越好
- 运用精细加工策略来思考刚刚掌握的信息对于学习复杂的编程概念特别有效。
- 大脑首先设法把接收到的新信息构建为图式,然后存储到长时记忆中
- 信息越符合现有的图式,大脑越容易记住它们。
- 记忆一经存储就能被改变,这一点或许会带来弊端
- 即使经历过同一件事,两个当事人的回忆也可能截然不同,因为他们各自的想法和观念会影响大脑存储记忆的方式。
- 精细加工可用于强化记忆的初始编码
- 主动思考希望记住的内容,并把这些内容与已有的记忆联系起来,同时引导新的记忆融入长时记忆已经存储的图式。
3.5 小结
掌握大量语法知识,提高代码分块能力
- 查找语法的用法会打断思路,影响工作效率。
利用抽认卡学习并记忆新的编程语法
- 卡片的一面写上文字提示,另一面写上代码。
经常复习新信息,延缓记忆衰退
- 新信息输入大脑后,需要不断重复,才能转化为长期记忆。
提取练习,增强记忆效果
- 在“谷歌一下”或“百度一下”之前设法记住信息。
延长练习的时间跨度,增强记忆效果
- 长时记忆中的信息存储为相关事实的连通网络。
主动对新信息进行精细加工,强化记忆网络
- 对新信息进行精细加工有助于强化连接新信息的记忆网络,从而提高提取强度。
第4章 阅读复杂的代码
工作记忆认知过程
- 工作记忆是大脑加工能力的基础
- 工作记忆是短期存储和加工信息的系统。
- 代码复杂会导致工作记忆过载
- 代码太过复杂,以致很难完全理解。
减轻工作记忆负荷的方法
- 通过重构提高代码可读性
- 重构可以帮助理解代码的结构和逻辑。
- 创建依赖图和状态表
- 依赖图和状态表有助于减轻工作记忆的认知负荷。
4.1 为什么复杂的代码难以理解
复杂的代码难以理解的原因
- 程序员可能需要依靠中间值来理解代码,表明大脑缺乏加工能力
- 阅读复杂的代码时会涉及短时记忆和长时记忆,工作记忆代表大脑进行思维活动、孕育新想法以及解决问题的能力。
- 工作记忆与短时记忆的区别
- 短时记忆的作用是记忆信息,工作记忆的作用则是加工信息。
- 短时记忆一次通常只能存储2~6个信息元素,工作记忆一次只能加工2~6个信息元素,这种能力称为认知负荷。
与程序设计相关的3种认知负荷
- 内部认知负荷对代码阅读的影响
- 内部认知负荷是由问题本身引起的认知负荷。
- 程序设计中经常用固有复杂性一词来描述问题的内在因素,而在认知科学中,这些因素会产生内部认知负荷。
- 外部认知负荷对代码阅读的影响
- 外部认知负荷是由外部因素引起的认知负荷。
- 外部认知负荷的根源因人而异,程序员越熟悉某个概念,这个概念产生的认知负荷就越少。
4.2 减轻认知负荷的方法
重构
- 重构通过调整代码来改进其内部结构,但不改变代码的外部行为
- 如果某段代码特别长,那么程序员可能希望把逻辑拆分为多个函数。
- 如果代码库出现重复,那么程序员可能希望把所有重复的代码集中在一处以提高复用率。
- 认知重构同样是对代码库进行调整而不改变其外部行为,但目的不是使代码更容易维护,而是方便程序员在现阶段阅读和理解
- 认知重构有时涉及反向重构,这种重构会降低可维护性。
- 方法内联能够减轻外部认知负荷,或许可以帮助程序员读懂调用方法的代码。
- 程序员的先验知识各不相同,因此认知重构往往因人而异。
替换不熟悉的语言结构
- 如果程序员在阅读代码时遇到不熟悉的编程概念,则表明大脑缺乏知识,可以用另一种更熟悉的语言结构来替换不熟悉的语言结构
- lambda表达式和列表推导式是缩短代码长度、提高代码可读性的有效手段,但许多程序员对它们知之甚少,阅读这两种结构时很难做到像阅读for循环或while循环那样得心应手。
- 在阅读复杂的代码时,这类高级语言结构就可能导致工作记忆出现过载情况。为避免增加工作记忆的外部认知负荷,阅读复杂的代码时最好用熟悉的结构替换不太熟悉的结构。
在抽认卡两面写上等价的代码可以显著增强学习效果
- 对于这类高级的编程概念,建议在抽认卡两面都写上代码以增强学习效果:一面是没有使用高级概念(例如三元运算符或lambda表达式)的普通代码,另一面是使用高级概念的等价代码
- 如果程序员经常为学习列表推导式等编程概念而发愁,那么不妨考虑为它们制作一些抽认卡。
4.3 利用记忆辅助工具解决工作记忆过载的问题
工作记忆过载的原因
- 程序员可能不清楚应该阅读哪部分代码
- 如果大脑接收的信息过多,则会超出工作记忆的加工能力。
- 在阅读关联性很强的代码时,大脑会尝试双管齐下
- 一是理解每一行代码,二是理解代码结构以决定从哪里继续阅读。
利用记忆辅助工具解决工作记忆过载的问题
- 绘制依赖图有助于理解代码流,遵循逻辑流程来阅读代码
- 圈出所有变量,将相似的变量连起来。
- 圈出所有方法/函数调用,将方法/函数及其声明连起来。
- 圈出所有类的实例,将类及其实例连起来。
- 创建状态表来分析执行复杂计算或涉及大量计算的程序
- 列出所有变量,创建状态表并在每列填入一个变量。
- 在每行填入一个不同的代码执行过程。
- 阅读各部分代码,然后把每个变量的值填入表中合适的行和列。
- 结合使用依赖图和状态表厘清程序脉络,减轻工作记忆的认知负荷
- 依赖图主要用于判断代码的组织方式,状态表则主要用于判断代码执行的计算。
- 遇到不熟悉的代码时,不妨首先创建依赖图和状态表来全面梳理代码的内部机制,以此作为理解代码的基础。
4.4 小结
认知负荷理论
- 认知负荷代表工作记忆的信息加工能力,认知负荷过高会导致大脑无法正确加工代码。
程序设计中的认知负荷
- 程序设计涉及两种认知负荷:内部认知负荷和外部认知负荷
- 内部认知负荷源于代码的固有复杂性。
- 外部认知负荷则会在无意中增加代码加工的难度(与代码的表现形式有关),程序员缺乏相关知识也是产生外部认知负荷的原因。
减轻认知负荷的方法
- 程序员根据先验知识将代码重构为自己更熟悉的形式,从而减轻外部认知负荷。
- 绘制依赖图有助于理解复杂且相互关联的代码。
- 创建包含变量中间值的状态表有助于阅读涉及大量计算的代码。
第二部分 代码思考
第二部分:代码思考
- 第二部分将从代码阅读转向代码思考,剖析如何深入理解程序并避免出现思维方面的错误。
第5章 深入理解代码
程序中不同的变量角色
- 变量在程序中扮演着各种角色,包括存储数据、控制程序流程等
- 变量可以是整数、浮点数、字符串等基本数据类型,也可以是数组、对象等复杂数据类型。
- 变量的值可以被修改,也可以被读取。
- 变量可以通过条件语句、循环语句等控制程序流程。
比较文本结构知识与计划知识
- 文本结构知识关注代码使用的语法概念,计划知识关注代码编写者的意图
- 文本结构知识包括变量声明、函数定义、控制结构等。
- 计划知识包括代码的设计目标、实现思路、优化方案等。
比较阅读和学习自然语言与阅读和学习代码
- 阅读代码所需的技能与阅读自然语言所用的技能密切相关
- 阅读代码需要理解代码的语法、结构和逻辑。
- 阅读自然语言需要理解语言的语法、语义和语境。
- 程序员可以从研究如何阅读自然语言中得到很大启发,从而提高自己的代码理解能力。
深入理解代码的策略
- 推理代码编写者的理念、思路和决策
- 利用图式进行代码分块,提高代码思考的效率。
- 运用阅读自然语言的策略来阅读代码。
5.1 变量角色框架
变量角色框架的定义和作用
- 变量角色框架描述了变量在程序中承担的任务
- 变量之所以难以理解,是因为大多数程序员的长时记忆没有建立起能够关联变量的有效图式。
- 既不太笼统也不太具体的变量名对程序员更有帮助。
变量不同,承担的任务也不同
- 变量角色框架可以反映出变量在行为方面的差异
- upperbound的角色是固定值,负责存储用户最新输入的上界值。
- counter的角色是步进器,负责在循环中进行迭代。
- max_prime_factors的角色是最佳持有器,负责存储搜索过程中的最大值。
- factors的角色同样是最近持有器,负责存储当前的质因数个数。
涵盖大多数变量的11种角色
- 11种角色就能描述大多数变量
- 固定值:如果变量的值在初始化操作后不会发生变化,那么变量的角色就是固定值。
- 步进器:在循环中进行迭代时,总会有一个变量负责遍历值列表。
- 标志:如果变量用于指示程序执行情况,那么它的角色就是标志。
- 步行器:步行器变量和步进器变量的作用都是遍历数据结构,但二者的遍历方式有所不同。
- 最近持有器:在遍历值列表的过程中,如果变量用于保存最新的一个值,那么它的角色就是最近持有器。
- 最佳持有器:遍历值列表的目的往往是为了查找某个值。如果变量用于保存最精确或目前为止最接近的搜索结果,那么它的角色就是最佳持有器。
- 收集器:如果变量用于收集数据并聚合为一个值,那么它的角色就是收集器。
- 容器:这种数据结构用于存储多个可以添加或删除的元素,列表、数组、栈或树都属于容器。
- 跟随器:某些变量的当前值总是依赖于其他变量的前一个值。
- 组织器:有时候,变量在进一步处理前必须先转换为某种形式。
- 临时:临时变量的值只在短时间内使用,通常命名为temp或t。
5.2 角色和范式
变量角色的概念和适用性
- 变量角色框架是加深代码理解、提高沟通效率的有效手段
- 变量角色框架是Sajaniemi设计的新专业词汇供程序员在讨论变量时使用。
- 变量角色框架适用于所有编程范式,包括函数式语言和面向对象编程
- 从5.1.2节讨论的收集器变量可以看到,函数式语言也支持这种角色。
- 在面向对象编程中,同样能找到5.1.2节概述的变量角色。
角色的优点
- 变量角色框架能帮助学生理解源代码,提高学习效果
- 研究表明,变量角色框架能帮助学生理解源代码,使用这种框架的学生比不使用这种框架的学生有更出色的表现。
- 变量角色框架有助于开发团队提高沟通效率
- 对于开发团队特别有帮助,因为它给出新的专业词汇供程序员在讨论变量时使用。
匈牙利命名法
- 匈牙利命名法要求变量名体现出变量类型
- 例如,strName是表示字符串名称的变量,而lDistance是表示距离的长整型变量。
- 匈牙利命名法在20世纪70年代首先在基本组合编程语言中得到广泛应用
- 业界普遍认为C语言从BCPL发展而来。
- 匈牙利命名法在许多人看来,只会使变量名变长,从而更难阅读
- 显示变量类型目前已成为大多数IDE的标准功能。
应用型匈牙利命名法和系统型匈牙利命名法
- 系统型匈牙利命名法是将变量类型写入变量名的做法
- 这种命名约定其实并非Simonyi的原意。
- 应用型匈牙利命名法是Simonyi提出的命名约定的更具语义性版本
- 变量的前缀有更明确的含义,而不是只标明变量类型。
- 应用型匈牙利命名法的一些建议与Sajamieni设计的变量角色框架颇为类似
- 例如,Simonyi使用前缀t表示临时值,并提出将min和max分别作为数组最小值和最大值的前缀,这些建议与变量角色框架中的最佳持有器不谋而合。
5.3 加深对程序的了解
代码理解的两个层次:文本结构知识和计划知识
- 文本结构知识代表对程序的表面理解,计划知识代表理解代码编写者在开发程序时计划实现的目标
- 文本结构知识包括了解关键字的作用或变量的角色。
- 计划知识隐藏在变量及其角色中,在程序员分析代码的结构和连接方式时会变得更加明显。
程序理解的不同步骤
- 程序员对代码的理解分为4步:寻找焦点、以焦点为基础逐步了解代码、通过一系列相关的实体来理解某个概念、理解涉及多个实体的不同概念
- 第1步:寻找焦点。焦点既可以是代码的入口点,也可以是由于其他原因而引起程序员注意的某一行代码。
- 第2步:以焦点为基础逐步了解代码。梳理代码中存在哪些关系。从焦点开始,圈出所有起作用的相关实体(变量、方法和类)。
- 第3步:通过一系列相关的实体来理解某个概念。分析一段代码的调用模式或许有所启发。
- 第4步:理解涉及多个实体的不同概念。程序员希望从更高层次理解代码涉及的不同概念,例如代码中出现的各种数据结构以及应用于这些数据结构的操作和约束条件。
深入理解某段代码的4个步骤
- 第1步:寻找代码的焦点。由于不是修复错误或添加功能,因此入口点可能就是代码的起点(例如main()方法)。
- 第2步:观察IDE或打印在纸上的代码,确定与焦点相关的代码切片。为此可能需要重构部分代码,把切片中涉及的相关代码移到一起。
- 第3步:根据第2步的情况,记下自己掌握的信息。例如,代码包括哪些实体和概念?它们之间的关系如何?
- 第4步:把代码中出现的概念以及自己的理解整理成表。第3步创建的实体列表和第4步创建的概念列表都是重要资料,可以用作代码文档。
5.4 阅读代码和阅读文本有相似之处
阅读代码和阅读自然语言的相似之处
- 阅读代码时大脑的活动情况与阅读自然语言时相似
- 研究表明,程序理解会激活与工作记忆和语言处理有关的大脑区域。
- 能学会法语,就能学会Python
- 研究发现,语言能力是预测编程能力更重要的指标,方差为17%。
- 学习编程语言的能力取决于学习自然语言的能力。
阅读代码的策略
- 人们在阅读文本时往往会采取多种策略,例如先扫视一遍文本,再决定是否花时间仔细阅读
- 阅读过程中,人们也可能有意识地浏览文字配图以帮助自己理解文本以及上下文,有时还会总结阅读要点或圈出最重要的内容。
- 程序员通过扫视代码来了解程序的功能
- 研究人员在统计被试者的代码阅读时间后发现,他们在最初30%的时间里浏览了超过70%的代码行。
新手程序员和资深程序员的代码阅读方式有所不同
- 新手程序员的代码阅读方式更加线性,而且会更频繁地追踪调用栈
- 与资深程序员相比,新手程序员在阅读文本时,大约80%的眼球运动是线性的;而在阅读代码时,75%的眼球运动是线性的。
- 如果不具备线性阅读的条件,那么新手程序员往往会追踪调用栈而不是从上往下阅读代码。
5.5 运用文本理解策略来阅读代码
阅读代码与阅读自然语言的认知相似性
- 阅读代码和阅读自然语言所用的认知技能很接近
- 阅读自然语言的研究成果或许也适用于阅读代码。
激活先验知识
- 主动思考代码元素有助于工作记忆检索长时记忆存储的相关信息
- 为有意识地激活先验知识,不妨用一段固定的时间(例如10分钟)来分析代码并了解它的作用。
监测
- 阅读代码时,及时了解所阅读的内容以及是否理解这些内容很重要
- 建议把程序打印出来,并借助标记变量角色时所用的图标来标注有把握的代码行和拿不准的代码行。
- 标出拿不准的内容不仅便于监测程序的理解情况,还能提高寻求帮助的效率。
确定不同代码行的重要性
- 思考哪些代码行很重要可能非常有用
- 建议程序员有意识地培养这方面的能力。
- 如果把代码打印出来,那么不妨使用感叹号来标记重要的代码行。
推断变量名的含义
- 代码结构本身包含程序的大量信息,循环和条件语句就是一例。程序元素的名称(例如变量名)也能提供线索,有时可能需要推断它们的含义
- 从第2章的讨论可知,变量名是提示代码作用的重要线索(信标),因此阅读代码时应该特别予以注意。
- 为培养推断变量名含义的能力,一种方法是逐行阅读代码,并列出变量名、类名、方法名、函数名等所有标识符,即使完全不了解代码的作用也可以这样做。
视觉化
- 可以通过创建状态表、跟踪代码流等手段将代码以视觉化方式呈现出来,以便加深理解
- 如果需要深入理解一段极其复杂的代码,那么列出变量执行的所有操作会有帮助。
提问
- 阅读代码时问自己一些问题有助于理解代码的目标和作用
- 可以参考前几节给出的众多示例问题,部分更有价值的问题如下:代码中最核心的5个概念是什么?采用哪些策略来确定核心概念?代码中最核心的5个计算机科学概念是什么?可以确定代码编写者所做的哪些决策?这些决策依赖于哪些假设?这些决策有哪些优点?这些决策可能存在哪些不足?是否还有更好的决策?
摘要
- 用自然语言编写代码摘要是深入理解代码实质的有效手段
- 代码摘要还能作为附加文档供个人参考,甚至在没有文档的情况下作为正式的代码文档供团队参考。
5.6 小结
阅读不熟悉的代码时确定变量角色的作用
- 确定变量角色有助于加深理解
- 阅读不熟悉的代码时,确定变量角色(例如步进器或最佳持有器)有助于加深理解。
理解代码时文本结构知识和计划知识的作用
- 文本结构知识了解代码使用的语法概念,计划知识理解代码编写者的意图。
阅读代码与阅读自然语言之间的相似之处
- 阅读代码与阅读自然语言之间存在诸多相似之处,可以根据学习自然语言的能力预测学习编程语言的能力
- 通常可以借助视觉化、摘要等策略深入理解自然语言文本,这些策略同样适用于深入理解代码。
第6章 更好地解决编程问题
运用模型提高推理程序问题的效率
- 了解代码分析所涉及的心智表征有助于程序员在解决问题时举一反三,提高推理代码和解决问题的效率。
分析为什么思考问题的方式会影响解决问题的方式
- 专业程序员经常需要权衡不同的问题解决方案,决定采用哪种方案颇费思量
- 应该优先考虑易用性,还是把性能放在首位?
- 是否有必要考虑今后可能对代码所做的修改,还是集中精力处理当前的任务?
探讨如何运用模型更有效地思考代码和解决问题
- 本章将介绍两种与模型有关的方法,程序员可以利用这些方法强化长时记忆并改善工作记忆。
研究通过改善长时记忆来学习解决问题的新方法
- 程序员在解决问题时如何与计算机打交道,某些情况下可以抽象出许多细节
- 在设计用户接口时,一般不需要关心操作系统的大多数具体细节。
- 在实现机器学习模型或开发手机应用程序时,则必须考虑运行代码的设备是否满足要求。
练习运用模型来改善工作记忆,从而提高解决问题的能力
- 程序员需要从适当的抽象层面思考问题,本章讨论的第二种框架可以帮助程序员从适当的抽象层面思考问题。
6.1 借助模型来思考代码
模型的定义和作用
- 模型是对现实的简化表征,主要目的是帮助人们思考问题并最终解决问题
- 封底计算(估算)是一种模型,软件系统的实体关系图也是一种模型。
使用模型的优点
- 模型有助于团队成员相互交流程序的相关信息
- 其他人可以根据程序员创建的状态表判断变量的所有中间值,从而帮助自己理解代码的作用。
- 模型有助于解决问题,减轻认知负荷
- 工作记忆能同时加工的信息元素有限,当工作记忆接近饱和状态时,构建模型是减轻认知负荷的有效方法。
- 模型有助于长时记忆检索相关记忆,所以对解决问题大有裨益。
模型对问题解决的影响
- 不同的模型对问题解决的影响不同
- 程序员很清楚表征的重要性及其对问题解决的影响。
- “小鸟和火车”问题可以更好地体现出表征的重要性。
- 编程语言对问题表征的影响
- 有些编程语言会限制可能表征的数量,这对解决问题既有利也有弊。
- Java可以通过创建类来表征各种问题,所以也能处理与矩阵有关的问题,缺点是需要创建矩阵类,因此会增加工作量。
6.2 心智模型
心智模型的定义与重要性
- 心智模型是程序员在思考问题时借助的模型,模型不同,对分析问题所起的作用也不同。
- 心智模型在工作记忆中创造出某种抽象,可以用来推理当前的问题
- 心智模型是存储在长时记忆中并按需提取,还是在分析代码时由工作记忆构建?了解大脑如何加工心智模型有助于我们更好地运用这些模型。
心智模型的特征
- 心智模型可以充分表征问题,但比现实更简单也更抽象
- 无论是心智模型还是脑外构建的模型,二者的重要特征都是可以充分表征问题,但比现实更简单也更抽象。
学习新的心智模型
- 类型不同、相互矛盾的心智模型能够在大脑中共存
- 为简单起见,我们不妨认为文件“存放”在文件夹里,但心里清楚文件其实是硬盘存储的信息集合。
- 学习编程的过程往往是逐步掌握新模型的过程。
- 有人认为,随着对事物的认识不断深入,大脑会清除原先“错误”的心智模型,代之以更准确的心智模型。但是从前几章的讨论可知,长时记忆存储的信息不太可能完全消失。
- 多个心智模型可以同时处于活跃状态,这些模型并非总是泾渭分明。正因为如此,原先的心智模型存在突然“乱入”的可能性,这种情况在大脑承受的认知负荷较高时更容易出现。
如何运用心智模型提高代码分析的效率
- 构建准确而具体的心智模型有助于分析复杂的系统
- 从构建局部模型入手。
- 列出代码库中所有相关对象以及对象之间的关系。
- 回答系统的相关问题,并根据答案来完善模型。
- 心智模型既存在于长时记忆中,也存在于工作记忆中
- 长时记忆存储的心智模型会影响工作记忆构建的心智模型。
6.3 概念机器
概念机器的定义
- 概念机器代表一种可以随时与之交互的机器,与为某种事物(例如物理或化学)构建的心智模型有很大不同
- 我们固然可以借助科学实验来认识客观世界,但也有许多时候不具备实验条件,或者至少难以安全地开展实验。
- 概念机器是一种抽象模型,程序员可以利用这种模型从抽象层面推理计算机在特定时间的运行情况
- 以某个值为12的变量x为例,程序员在大多数情况下并不关心存储值的内存地址以及指向该地址的指针,只要把x看作一个存在于某处、保存当前值的实体即可。
概念机器的例子
- Ben du Boulay在研究Logo语言时提出了概念机器的设想
- du Boulay用工厂工人来类比语言执行模型。
- 程序员可能考虑使用代码执行机器的抽象表征来阅读和编写代码
- 在思考如何利用编程语言进行计算时,程序员经常把计算机的执行过程比作数学家的工作。
概念机器适用的不同层面
- 有些概念机器适合在编程语言的层面使用,并抽象出底层机器的所有细节
- 例如,“把变量抽象为盒子”适用于编程语言的层面和编译器/解释器的层面。
- 有些概念机器能更好地描述实体计算机如何执行程序
- 例如,把栈比作一摞文件。
6.4 概念机器和语言描述
概念机器和语言描述
- 程序员使用概念机器推理计算机的运行方式,并用它来分析代码
- 程序员将变量描述为“保存”值的实体,类似于把变量比作存放值的盒子的心智模型。
- 编程语言会吸纳通常用来解释事物原理的概念机器,它们甚至已经成为语言的一部分
- 例如,指针的概念存在于许多编程语言中,不少IDE则支持程序员查看某个函数的“调用”位置。
概念机器可以扩展
- 编程语言未必只有一种放之四海而皆准的概念机器,而是可以包括一系列相互关联的概念机器
- 例如,在学习基本类型时,程序员可能把变量看作存放值的一个盒子;而在接触到复合类型后,程序员可能把数组看作一摞盒子,每个盒子存放一个简单值。
- 为理解编程语言概念而构建的抽象集可以进行扩展
- 例如,可以将没有参数的函数看作由几行代码构成的包,将包含输入参数的函数看作把一组值装进背包,然后带到调用位置的旅行者,将还包含输出参数的函数也看作把一个值装进背包的旅行者。
不同的概念机器可能催生出相互矛盾的心智模型
- 概念机器可能催生出相互矛盾的心智模型
- 例如,将变量描述为盒子的概念机器不同于将变量描述为名称标签的概念机器,二者无法合并为一个一致的心智模型。
- 不应贸然采用现实生活中的对象和操作来描述编程概念和计算机相应的工作原理
- 虽然这些隐喻或许有一定帮助,但也可能造成困扰——如果旧的心智模型还留存在长时记忆中或偶尔出现在工作记忆中,则更令人无所适从。
6.5 概念机器和图式
概念机器和图式
- 概念机器是思考编程的有效手段
- 逻辑清晰的概念机器将编程概念与大脑中业已形成有效图式的日常概念联系在一起。
图式的重要性
- 图式描述了长时记忆存储信息的方式
- “盒子”这个概念也许会使人产生强烈的联想,把变量比作盒子不会增加认知负荷。
- 在解释某个概念时务必选择对方熟悉的类比
- 大象在印度农村家喻户晓,因此在向这些地区的儿童讲解计算机的功能时,一些教育工作者会将计算机比作大象,将程序员比作驯象师。
概念机器是否具有语义性
- 概念机器不等同于语义学
- 语义学旨在利用数学方程来精确描述计算机的工作原理,其目的不是抽象出细节,而是准确且完整地解释细节。
6.6 小结
表征问题的方式对思考问题的方式的影响
- 把客户看作列表而非集合,存储和分析客户对象的方式也会随之变化。
心智模型的定义和影响
- 心智模型是大脑在思考问题时形成的心智表征
- 多个相互矛盾的心智模型可以共存。
概念机器的定义和作用
- 概念机器是描述实体计算机如何运行的抽象模型
- 概念机器可以用来解释编程概念和分析代码。
- 程序员可以借助概念机器将现有的图式应用于编程,从而帮助自己理解编程概念。
- 不同的概念机器有时相得益彰,有时则可能催生出相互矛盾的心智模型。
第7章 迷思概念:错误的思维方式
学习多门编程语言时可能出现的错误
- 学习新语言的过程中可能会形成许多错误假设
- 例如,就算程序员不使用file.close()函数,Python也能关闭通过open()函数打开的文件。
- C语言的要求则有所不同,程序员必须使用fclose()函数来关闭流。
如何避免因语言差异而产生挫败感和错误
- 尽可能运用现有知识来增强学习新语言的效果
- 了解不同语言对各种概念的约定。
程序设计中特有的各种迷思概念
- 迷思概念的成因以及它们会导致哪些错误
- 了解大脑可能形成的迷思概念有助于程序员及早发现代码错误。
如何避免思考问题时的迷思概念
- 探讨思考问题时如何避免迷思概念并防止出现错误。
7.1 为什么学习第二门编程语言比学习第一门编程语言更容易
学习第二门编程语言比学习第一门编程语言更容易的原因
- 长时记忆存储的关键字和心智模型有助于程序员理解代码
- 如果程序员了解Java,那么学习Python时就不会感到吃力,因为已经熟悉变量、循环、类、方法等基本的编程概念。
- 长时记忆存储的编程知识可以从两方面促进新概念的学习:学习中迁移和学习迁移
- 如果已经对程序设计(或其他任何领域)有深入了解,那么学习时就更容易做到举一反三。
- 如果已经掌握的知识可以应用于完全陌生的情境,则会产生学习迁移。
充分挖掘现有编程知识的潜力
- 影响迁移量的因素包括掌握、相似性、情境、关键属性、联想和情绪
- 针对任务的熟悉程度,完成任务所需的知识已经存储在长时记忆中。越了解一项任务,就越有可能将其应用到其他领域。
- 两项任务之间的共同点。例如,与使用陌生的编程语言实现自己不熟悉的算法相比,使用陌生的编程语言实现自己熟悉的算法更容易。
- 环境的相似程度。任务之间的相似性很重要,执行任务的情境也很重要。
- 在多大程度上了解可能从哪些知识中获益。
- 在多大程度上感受到任务之间的相似程度。
- 对任务的感觉。情绪同样是影响迁移的因素之一。
不同的迁移类型
- 低阶迁移指无意识地将已经掌握的技能应用于新情境,高阶迁移指有意识地将已经掌握的技能应用于新情境
- 如果程序员在换用新的IDE后不假思索地按下“Ctrl+C”和“Ctrl+V”,则表明产生低阶迁移的可能性很大。
- 程序员知道声明变量是大多数语言的规定,所以在使用一门新语言进行开发时可能也会这样做。
- 近迁移指将已经掌握的知识应用于相似的领域,远迁移指将已经掌握的知识应用于差异较大的领域
- 例如,微积分与代数、C#与Java之间的迁移属于近迁移。
- 例如,拉丁语与逻辑学、Java与Prolog之间的迁移属于远迁移。
已经掌握的知识:是福还是祸
- 正迁移发生时,大脑可以利用长时记忆已经存储的其他心智模型,所以不必从头开始为新情境构建全新的心智模型
- 熟悉Java的程序员已经建立起循环结构的心智模型,知道循环结构由计数器变量、循环体和终止条件这3个要素构成。由于大部分编程语言的循环结构包括这些要素,因此程序员在学习新语言时很清楚如何构建循环结构的心智模型。
- 负迁移指现有知识妨碍到学习新知识
- 例如,Java变量遵循“先赋值,后使用”的原则,经验丰富的Java程序员或许认为Python也是如此:所有变量必须经过初始化,否则编译器会报错。
- 即使是相似性很高的编程语言,也存在发生负迁移的风险。以Java和C#为例,这两门语言的心智模型虽然相似,但不完全一样。
迁移有难度
- 除非情境之间具有足够的相似性,否则很难产生迁移
- 远迁移指知识从一个领域“跳到”另一个不太相似的领域,自发产生这种迁移的可能性很小。
- 掌握一门编程语言在学习另一门编程语言时未必能派上用场
- 从本节的讨论可知,产生远迁移(例如从SQL到JavaScript的迁移)的可能性很小。
- 在学习一门新语言时,程序员也许要掌握大量新语法和新策略才能像已经掌握的语言那样做到游刃有余。
7.2 迷思概念:思维中存在的错误
迷思概念的定义和影响
- 迷思概念是不正确、在不同情况下始终存在、使人坚信不疑的认知
- 例如,许多人认为辣椒籽是辣椒最辣的部分,但它其实一点儿都不辣。
- 迷思概念可能源于简单的疏忽,也可能有更深层次的原因,那就是程序员对当前代码所做的假设有误
- 例如,他们可能觉得实例应该在代码中的其他位置完成初始化,也许他们坚信选择的方法正确无误,也许他们认为所用的数据结构可以确保不会出现元素访问越界的情况。
通过概念转变来消除迷思概念
- 概念转变是在新知识的基础上改变、取代或吸收现有的概念
- 例如,具备Java背景的程序员在学习Python时不得不忘掉Java的某些语法规定。
- 概念转变学习比常规学习的难度更大,需要下大力气“忘掉”现有的编程语言知识
- 学会在编程时思考类型需要进行概念转变,所以可能要花很长时间。
抑制迷思概念
- 记忆提取将随着时间的推移而减少,但关于错误思维方式的记忆依旧存在
- 即使我们不希望大脑唤醒陈旧的记忆,这种情况也仍然可能发生。
- 抑制往往与自我意识、克制或害羞感联系在一起,但学界近年来开始接受这样一种观点:当抑制性控制机制处于活跃状态时,正确的概念就可能“战胜”错误的概念
- 如果依靠推理而不是凭借直觉进行判断时出现“稍等片刻”的感觉,则表明大脑可能在努力抑制不正确的想法。
与编程语言有关的迷思概念
- 编程新手可能存在的162种迷思概念,包括基本类型的赋值会存储方程或未解析的表达式、一旦条件变为假,while循环就会结束等
- 例如,有些程序员误认为total的值与maximum的值以某种方式联系在一起。
- 这种迷思概念的有趣之处在于完全说得通,因为有些编程语言确实在一定程度上把变量之间的关系表示为方程组,Prolog就是一例。
- 受到这种迷思概念的困扰,程序员认为程序在执行每一行代码时都会检查终止条件,当条件变为假时便立即结束循环。
- 这种迷思概念可能与关键字while的含义有关。
- 33号迷思概念表明,英语单词(关键字)的含义可能会妨碍到程序员理解编程概念。
- 46号迷思概念:调用和签名需要使用不同的变量名以传递参数。
- 受到这种迷思概念的困扰,程序员往往认为变量名不能重复使用,方法/函数内部的变量名也是如此。
在学习新的编程语言时避免形成迷思概念
- 应该明白,即使充分相信自己的水平,也不能保证自己的判断就一定正确,所以做到心态开放、不抱成见很重要
- 建议程序员有意识地研究常见的迷思概念以少走弯路。
- 确定何时做出的假设有误以及哪些假设有效并不容易,因此列出常见的迷思概念并在学习时进行对照也许有一定帮助。
- 如果能找到按照相同顺序学习同一门编程语言的程序员,那么不妨听听他们的建议。
判断代码库中存在的迷思概念
- 程序员使用的代码库同样可能成为迷思概念的来源
- 每当程序员根据之前的编程经验(例如语言、框架、库、代码领域、变量名和其他标识符的含义)或其他程序员的想法对代码做出假设时,都可能形成迷思概念。
- 结对编程或小组编程是排查迷思概念的手段之一
- 如果大家相互交流各自的想法和假设,那么很快就能找到矛盾之处以及存在迷思概念的程序员。
- 意识到自己的错误并不容易,因此应该随时运行代码或利用测试套件来核实自己所做的假设是否正确
- 如果坚信某个值不会小于零,那么不妨编写一项测试加以验证。
- 文档是避免迷思概念的另一种手段
- 如果程序员发现自己对代码库中某个方法、函数或数据结构的认识有误,那么除了编写测试,还可以考虑在相关位置添加文档,以提醒自己和他人不要重蹈覆辙。
7.3 小结
长时记忆存储的知识可以迁移到新情境
- 现有知识对学习新知识或完成新任务起到促进作用,则表明产生了正迁移。
- 现有知识对学习新知识或完成新任务起到阻碍作用,那么两个领域之间的知识迁移就属于负迁移。
主动在长时记忆中检索相关信息有助于增强正迁移的效果
- 运用精细加工实践有助于增强正迁移的效果
- 精细加工实践包括对信息进行深入的思考、解释和联系。
程序员可能受到迷思概念的困扰
- 如果程序员确信自己的判断正确但其实不正确,则表明他们可能受到迷思概念的困扰
- 迷思概念是程序员对某个问题的错误理解或信念。
纠正迷思概念需要新的心智模型
- 仅仅认识到自己的判断不正确很难彻底消除迷思概念
- 如果希望纠正迷思概念,就要用新的心智模型来取代陈旧、错误的模型。
即使大脑建立起正确的模型,迷思概念也依然可能再次出现
- 大脑在处理信息时,可能会受到先前经验的影响,导致迷思概念的再次出现。
在代码库中编写测试和文档有助于消除迷思概念
- 通过编写测试和文档,可以验证程序员的理解和实现是否正确,从而消除迷思概念。
第三部分 代码编写
构建优质代码
- 提高代码可读性
- 使用有意义的标识符。
- 遵循编码规范。
- 避免含义不明确的标识符和代码异味
- 避免使用过于复杂或难以理解的代码。
- 减少不必要的注释和文档。
- 在解决复杂的问题时提高代码编写技巧
- 善于分解问题,将其拆分为更小的、可管理的部分。
- 使用合适的数据结构和算法来解决问题。
- 善于利用现有的工具和资源来提高工作效率。
第8章 提高命名的质量
命名的重要性
- 高质量的标识符便于大脑从长时记忆中检索出已经掌握的代码领域的相关信息。
- 低质量的标识符可能导致程序员误判代码,从而产生迷思概念。
命名的困难性
- 工作记忆需要全力构建心智模型并利用模型来推理代码,构思一个优雅的标识符极有可能加重认知负荷
- 进行方案建模、问题解决等活动时,工作记忆需要全力构建心智模型并利用模型来推理代码。
- 大脑也许会承受很高的认知负荷,这种情况下,构思一个优雅的标识符极有可能加重认知负荷。
标识符对编程的影响
- 分析哪些类型的标识符更有利于代码理解
- 描述性的标识符更易于理解,能够表达出变量的用途和作用。
- 具有描述性的前缀和后缀可以使变量更具语义化。
- 讨论糟糕的标识符会产生哪些负面影响
- 糟糕的标识符可能导致代码难以阅读和理解。
- 糟糕的标识符可能使程序员产生误解,导致错误。
- 给出具体的命名准则
- 使用有意义的名称,避免使用无意义的缩写或单字母变量名。
- 使用描述性的名称,表达出变量的用途和作用。
- 避免使用过于复杂或难以理解的名称。
- 在可能的情况下,使用具有描述性的前缀和后缀。
8.1 命名为什么重要
命名的重要性
- 标识符是代码库的重要组成部分
- Eclipse的源代码约有200万行,其中33%的标记和72%的字符属于标识符。
- 标识符在代码审查中起到一定作用
- 25%的代码审查包含与命名有关的注释,而关于标识符的注释占比为9%。
- 标识符是最常见的文档类型
- 代码注释和标识符是阅读频率最高的“文档”。
- 标识符可以充当信标
- 变量名也是帮助代码阅读者理解程序的重要信标。
与命名有关的不同观点
- 标识符应该遵循语法规则
- 英国开放大学副高级讲师Simon Butler整理出一份问题清单,包括不同类型的规则,大多数规则和语法有关。
- 许多编程语言会制定变量命名的相关规范,如Python命名约定(PEP8)规定变量采用蛇形命名法,Java命名约定则规定变量采用驼峰命名法。
- 标识符在代码库中应该保持一致
- 代码库是否遵循一致的命名实践是最重要的判断标准。
- 避免命名实践前后不一符合我们对认知科学的了解。
最初的命名实践影响深远
- 标识符质量在程序开发的早期阶段就已定型
- Dawn Lawrie分析了采用C++、C、Fortran和Java这4门语言编写的78种代码库的186个版本,发现同一个代码库的命名实践并没有随着代码的发展而改善。
- 代码库的规模不会影响命名实践
- 研究结果表明,代码库的规模与质量之间没有必然联系,因此就标识符质量而言,代码库越大并不意味着质量越好(或越差)。
8.2 从认知的角度剖析命名
规范的命名方式对短时记忆有利
- 明确规定标识符的命名方式可能有助于短时记忆理解正在阅读的标识符
- Allamanis认为标识符在整个代码库中应该保持一致,这种命名实践可能有利于分块。
- Butler提倡使用遵循类似语法规则的标识符,例如标识符不能以下划线开头以及应遵守一致的首字母大写规定,这种命名实践使相关信息每次都能以相同的方式呈现给大脑,从而也能在一定程度上减轻阅读标识符时产生的认知负荷。
- 提高代码库中标识符的一致性,可以使用Naturalize工具
- Naturalize的开发者利用这种工具在现有代码库中创建了18个拉取请求,并给出标识符改进建议。
- Naturalize目前仅支持Java一种语言。
含义明确的标识符对长时记忆有利
- 命名变量或类时采用合适的领域概念有助于大脑在长时记忆中检索相关信息
- “customer”这类领域词会使长时记忆产生各种各样的联想,例如客户可能在购买产品、需要为客户定义姓名和地址等。
- 树这样的编程概念同样有助于大脑检索长时记忆存储的信息,例如树有根节点、可以遍历、可以进行扁平化处理等。
标识符可以包括不同类型的信息以帮助理解
- 把领域知识、编程概念、命名约定等三类信息纳入标识符
- 领域知识:标识符对思考代码领域有利。
- 编程概念:标识符对思考程序设计有利。
- 命名约定:某些情况下,标识符还包括长时记忆存储的命名约定信息。
评估标识符质量的时机
- 代码审查是评估标识符质量的良机
- 实施代码审查时,可以借助练习8-4来评估程序使用的标识符。
8.3 哪些类型的标识符更容易理解
使用完整单词构成的标识符更容易理解
- 专业程序员在阅读标识符由单词构成的程序时,平均每分钟可以多发现19%的错误
- Johannes C. Hofmeister的实验结果表明,比起阅读标识符由字母和缩写构成的程序,在阅读标识符由单词构成的程序时,被试者平均每分钟可以多发现19%的错误。
- 由完整单词构成的标识符比由缩写或单字母构成的标识符更容易理解
- Lawrie的实验结果表明,比起标识符由单字母构成的代码摘要,标识符由完整单词构成的代码摘要评分更高,二者的差距几乎有1分之多。
- 标识符越长,记忆难度就越大,所需时间也越多
- Lawrie发现标识符越长,记忆难度就越大,所需时间也越多。较长的标识符之所以不容易记住,不是因为长度问题,而是因为标识符包含的音节数量。
谨慎采用包含前缀标识符或后缀标识符的命名约定
- 程序员应该仔细评估这类命名约定,确保额外的信息不会增加记忆标识符的难度
- Lawrie建议在采用包含前缀标识符或后缀标识符的命名约定时要三思而行。
单字母标识符的使用需要考虑约定和联想
- 对于单字母标识符的使用,不同编程语言的约定大相径庭
- Perl中最常用的3个单字母标识符依次为v、i和j,而JavaScript中最常用的3个单字母标识符依次为i、e和d。
- 程序员在看到单字母时所产生的联想因人而异
- 大部分字母并没有约定俗成的含义。虽然绝大多数程序员在看到字母s时会想到字符串,在看到字母c时会想到字符,在看到字母i、j、k和n时会想到整数,但是除这些明显的情况外,程序员对其他字母代表的类型看法不一。
驼峰命名法和蛇形命名法对理解的影响
- 程序员和非程序员在判断采用驼峰命名法的标识符时,准确性都很高,但需要多花0.5秒才能找到
- Binkley发现,在判断采用驼峰命名法的标识符时,程序员和非程序员的准确性都很高。研究结果表明,被试者判断驼峰命名法的正确率比判断蛇形命名法高出51.5%。但更高的准确性是以牺牲时间为代价的:被试者要多花0.5秒才能找到采用驼峰命名法的标识符。
- 熟练掌握驼峰命名法的程序员能够更快地找到采用这种命名法的标识符,但接受过一种命名约定的培训似乎会对判断另一种命名约定产生负面影响
- Binkley在比较两组被试者的表现后发现,熟练掌握驼峰命名法的程序员能够更快地找到采用这种命名法的标识符。但研究结果也表明,接受过一种命名约定的培训似乎会对判断另一种命名约定产生负面影响:与完全没有受过培训的被试者相比,熟练掌握驼峰命名法的被试者在识别采用蛇形命名法的标识符时速度更慢。
8.4 标识符与代码错误之间的关系
低质量的标识符会引发更多代码错误
- Butler的研究发现,糟糕的命名实践与代码质量之间具有统计学意义上的关联性
- Butler以采用Java编写的开源仓库(包括Tomcat和Hibernate)为对象分析了标识符命名缺陷与代码质量之间的关系。
- 他利用这种工具评估了8个代码库,以找出糟糕的命名实践位于何处。
- Butler既检查了结构性命名问题(例如标识符是否包含连续的两条下划线),也检查了单词构成问题(例如标识符是否使用词典单词)。
- 标识符命名缺陷与代码质量之间具有统计学意义上的关联性。
- 糟糕的命名实践会增加代码阅读、理解和维护的难度,还可能导致代码出错
- 解决复杂的问题时同样可能引入错误,这些复杂的问题也许以其他方式与命名错误有关。
- 由于编写代码时需要解决棘手的问题,因此大脑可能会承受非常高的认知负荷。
- 如果代码领域很复杂,那么取个好名也未必容易,为此程序员需要绞尽脑汁思考高质量的标识符,而这样做很容易引发错误。
- 通过检查代码库也许能发现糟糕的命名实践位于何处,从而便于“顺藤摸瓜”,找到有改进余地的代码和可以避免的错误
- 虽然解决命名问题不一定对修复错误有直接帮助,但排查低质量标识符的另一个原因就在于此:标识符质量越高,理解代码就越容易,因此改进标识符可以在一定程度上间接减少错误,至少能够缩短修复错误的时间。
8.5 如何设计质量更高的标识符
名称模具
- 名称模具是程序员用来组合变量名的典型方式
- 表8-6列出了变量名,并经过归一化处理,因此max可以代表“max”或“maximum”,而benefit可以代表“benefits”。
- 使用不同的名称模具可以催生大量不同的变量名,降低“重名”的概率。
- 在同一个代码库中使用不同的模具并不明智
- 在变量名(本例是benefit)和构成变量名的不同元素中检索相关概念会增加不必要、不相干的认知负荷。
- 如果变量名相似,那么使用相同的模具也许更便于长时记忆检索相关信息。
- 每个代码库最好不要使用太多不同的模具,建议开发团队在项目启动前针对使用哪些模具达成一致
- 程序员不妨从现有的代码库入手,列出其中出现的变量名并确定目前所用的模具,然后决定是否继续使用这些模具。
运用Feitelson设计的三步模型来提高标识符质量
- 三步模型包括选择标识符要体现的概念、挑选代表每个概念的单词、根据这些单词设计标识符
- 第1步是选择标识符要体现的概念,这一步具有极强的领域特定性,思考标识符包含哪些概念也许是命名中最重要的决定。
- 第2步是挑选代表每个概念的单词。找到合适的单词通常并不难,显然应该选择一个在代码领域或整个代码库中使用的特定单词。
- 第3步是根据选定的单词来设计标识符,本质上是选择一个名称模具。
- 三步模型有助于提高命名质量
- Feitelson向被试者讲解了三步模型的用法并给出一个示例,接着要求他们运用模型重复第一项实验:选择变量名。
- 经过比较,两位评委认为第二项实验(使用三步模型)的命名质量优于第一项实验(未使用三步模型),比例为2:1。
8.6 小结
标识符质量衡量标准的不同看法
- 有学者提倡使用遵循语法规则(例如驼峰命名法)的标识符。
- 也有学者强调代码库中的标识符应该保持一致。
程序员对不同命名法的记忆和查找效率
- 程序员更容易记住采用驼峰命名法的变量。
- 能够更快找出采用蛇形命名法的变量。
低质量标识符与代码错误的关系
- 低质量标识符出现的位置很可能存在代码错误。
- 不意味着低质量标识符与代码错误之间必然存在因果关系。
标识符设计的原则
- 从便于理解的角度考虑,每个代码库最好不要使用太多名称模具。
- Feitelson设计的三步模型有助于提高命名质量。
第9章 避免低质量代码和认知负荷:两种框架
代码异味与认知负荷的联系
- 代码难以理解是因为大脑承受的认知负荷过高
- 当工作记忆接近饱和状态且大脑无法正确加工代码时,就会产生认知负荷。
低质量标识符与认知负荷的联系
- 结构性问题和标识符问题都会增加代码理解的难度,从而产生认知负荷
- 分析代码为什么难以阅读能帮助程序员写出更容易理解和维护的代码。
9.1 为什么存在异味的代码会加重认知负荷
代码异味简介
- Fowler提炼出了多种代码异味,包括过长的方法、提供太多功能的类、过于复杂的switch语句等
- 为消除代码异味,Fowler还提出了一系列他称为重构的策略。
- 代码异味既可能与方法有关,也可能与类有关,过大的类就是一例
- 这种类有时也称为上帝类,由于它提供的功能过多,因此不再是有意义的抽象。
- 如果类包括的方法和字段过少,那么同样无法成为有意义的抽象,Fowler将这种类称为冗赘类。
- 除了方法层面和类层面的代码异味,整个代码库也可能存在异味
- 如果代码库的不同位置出现相同或极为相似的代码,那么表明存在“重复的代码(代码克隆)”异味。
- 如果代码库包括多个相互之间不断传递信息的方法,则表明存在“过长的消息链”异味。
代码异味对认知的负面影响
- 过长的参数列表和复杂的switch语句会导致工作记忆过载
- 工作记忆一次只能加工2~6个信息元素,因此如果方法参数超过6个,那么大脑就很难记住它们。
- “过大的类”和“过长的方法”这两种代码异味的有害之处就在于阻碍有效分块
- 大脑在加工代码时会不断构建抽象,如果代码块过于庞大,则意味着可用于快速理解代码的本质特征过少。
- 重复的代码会导致分块错误
- 当遇到与foo()非常类似的方法调用goo()时,工作记忆可能会从长时记忆中获取foo()的相关信息,仿佛在提醒程序员“这些信息也许会派上用场”。
9.2 低质量标识符对认知负荷的影响
语言反模式的概念和特点
- 语言反模式是代码的语言元素与其角色不符
- 代码的语言元素被定义为代码的自然语言部分,包括方法签名、文档、属性名、类型或注释。
- 如果语言元素与它们的角色不符,则表明存在语言反模式。
- 语言反模式可能导致认知负荷加重
- 尽管可以凭直觉猜测标识符的含义,但语言反模式可能造成困惑,从而加重认知负荷。
认知负荷的测量方法
- 帕斯量表是一种常用的测量认知负荷的工具
- 帕斯量表只包括一个问题,是一种比较简单的调查问卷。
- 在阅读不熟悉的代码时,程序员可以借助帕斯量表来思考代码并评估为理解代码而付出的认知努力。
- 眼球测量法通过测量身体对某项任务的反应来估算认知负荷
- 科学家利用眼动追踪仪测量一个人的眨眼率,以确定注意力的集中程度。
- 认知负荷会影响眨眼率:任务越难,眨眼次数就越少。
- 皮肤测量法通过测量皮肤温度和汗液来评估认知负荷
- 这些生物特征测量技术听起来颇为诱人,不过研究表明它们往往与帕斯量表有联系。
- 大脑测量法通过测量大脑活动类型来评估认知负荷
- 研究人员利用功能性磁共振成像仪来测量大脑活动类型。
- 功能性近红外光谱技术被科学家用于深入研究语言负荷与认知负荷之间的关系。
语言反模式对认知负荷的影响
- 存在语言反模式的代码片段会产生更高的认知负荷
- 与不存在语言反模式的代码相比,被试者更关注存在语言反模式的代码。
- 在阅读存在语言反模式的代码时,被试者的平均含氧血红蛋白显著增加。
- 结构反模式不会增加被试者的平均认知负荷
- 相对于未做调整的对照代码片段,没有统计证据表明存在结构反模式的代码片段会增加被试者的平均认知负荷。
语言反模式令人困惑的原因
- 语言反模式可能导致学习迁移问题
- 遇到不熟悉的内容时,长时记忆会检索相关事实和经验,而假如标识符存在误导性,就会给大脑留下错误的印象。
- 语言反模式可能引发类似于代码克隆的“分块错误”
- 大脑或许仅凭名称便推测isValid是布尔型变量,从而不再仔细思考这个变量的实际作用。
9.3 小结
代码异味及其影响
- 代码异味表明代码存在结构性问题,会加重认知负荷
- 重复的代码不利于代码分块,过长的参数列表则会导致工作记忆不堪重负。
认知负荷的测量手段
- 通过生物特征传感器测量眨眼率或皮肤温度等方式测量认知负荷
- 一般来说,帕斯量表是自我评估认知负荷的有效工具。
语言反模式及其影响
- 标识符与所描述对象的实际作用不符表明代码库中存在语言反模式,会加重认知负荷
- 语言反模式可能是长时记忆在查找相关信息时检索到错误的事实所致。
- 语言反模式还可能使大脑对代码做出错误的假设,进而引发分块错误。
第10章 提高解决复杂问题的能力
问题解决的含义与重要性
- 问题解决是指找到问题的解决方案,并付诸实践的过程。
- 强化长时记忆有助于解决复杂问题
- 长时记忆是存储大量信息和知识的仓库,强化长时记忆可以帮助我们更好地理解和解决问题。
实现小技能的自动化
- 自动化小任务可以节省时间,提高解决复杂问题的能力
- 解决小事所花的时间越少,解决难题就越容易。
借鉴他人的代码并化为己用
- 借鉴他人的代码可以帮助我们快速解决问题,提高自己的问题解决能力
- 借鉴其他人所写的代码并化为己用,可以避免重复造轮子,提高自己的问题解决能力。
10.1 问题解决的实质
问题解决的三大要素
- 目标状态:希望实现的目标
- 一旦达到目标状态,就可以认为问题已经得到解决。
- 待解决问题的起始状态。
- 描述如何从起始状态达到目标状态的规则
- 编程问题的规则往往体现为约束条件。
状态空间
- 解决问题时,所有可以实施的步骤称为问题的状态空间
- 例如,在井字棋游戏中,任何一个可能画叉或画圈的格子都是状态空间。
- 在为网站添加按钮时,所有可能的JavaScript代码实现都是状态空间。
- 问题解决的实质是以最优方式遍历状态空间,争取用最少的步骤达到目标状态。
10.2 长时记忆在解决编程问题时所起的作用
问题解决本身是否属于认知过程
- 问题解决既不是一项通用技能,也不是一种认知过程
- 通用问题解决方法难以奏效的原因有两个,这两个原因都与长时记忆的作用有关。
培养长时记忆来解决问题
- 要想从更高的层次回答这个问题,就需要进一步了解大脑的思维方式
- 工作记忆在进行思维活动时并非处于封闭状态,而是得到了长时记忆和短时记忆的密切配合。
解决问题时起作用的两类记忆
- 长时记忆存储的各类记忆包括程序性记忆和陈述性记忆
- 程序性记忆是关于运动技能或无意识技能的记忆,有时称为内隐记忆。
- 陈述性记忆是能够有意识地回想起某些事实的记忆,例如“Java中for循环的语法格式为for (i = 0; i < n; i++) {}”。
- 情景记忆和语义记忆是陈述性记忆的两个子类
- 情景记忆就是通常所说的“记忆”,它是关于个人经历的记忆。
- 语义记忆则是关于意义、概念或事实的记忆。
10.3 自动化:构建内隐记忆
自动化:构建内隐记忆
- 自动化是提高问题解决能力的重要方法
- 一旦某项技能经过反复练习后成为本能,就可以认为这项技能已经实现自动化。
- 自动化可以减轻认知负荷,提高问题解决效率
- 如果能轻而易举地利用因式分解法解方程,那么就能进行更复杂的计算。
内隐记忆会随着时间的推移而变化
- 内隐记忆主要通过重复的方式产生
- 例如,儿童学习用勺子喝汤,反复多次后就可以掌握这项技能。
- 内隐记忆的形成分为认知阶段、联想阶段和自主阶段
- 认知阶段:大脑不仅要把接收到的新信息分解为较小的元素,而且必须有意识地思考如何完成当前的任务。
- 联想阶段:在这个阶段,大脑需要主动重复新信息,直到形成反应模式。
- 自主阶段(又称为程序性阶段),技能在这一阶段得到完善。
为什么自动化可以加快编程速度
- 自动化是大脑从情景记忆中提取信息的结果
- 采用因式分解法解方程、阅读信件这样的任务会产生新的记忆,它们是与任务有关的记忆实例。
- 实现自动化的标志是大脑完全依靠情景记忆而不是推理来完成任务
- 从记忆中提取信息的速度比积极思考当前的任务要快,而且可以在不经意间进行,因此已经实现自动化的任务处理起来既快速又轻松。
强化内隐记忆
- 刻意练习是提高尚未达到自主阶段的技能的有效方法
- 刻意练习指通过各种小练习来提高某项技能,直至达到信手拈来的程度。
- 刻意练习的方式多种多样,包括编写大量相似但不同的程序、修改程序等
- 如果希望熟练掌握for循环的用法,那么可以编写不同形式的for循环,包括从前向后遍历数组、从后向前遍历数组、改变循环计数器的步长等。
- 如果程序员疲于应付更复杂的编程概念,那么也可以考虑修改程序而不是从零开始编写程序。
10.4 从代码及其解释中汲取经验
认知负荷理论
- 认知负荷理论由澳大利亚心理学家John Sweller提出,深入研究了领域特定策略对问题解决能力的重要性
- Sweller在数学教学中教学生如何解代数方程,但发现他们从求解传统的代数问题中几乎学不到什么知识。
- Sweller和同事Graham A. Cooper在20世纪80年代做了一系列实验,发现使用样例的第一组学生的表现极为亮眼,解题速度是第二组学生的5倍。
样例效应
- 样例效应已经在针对不同年龄段和不同学科(包括数学、音乐、国际象棋、体育以及编程)的许多研究中得到重复
- 第一组学生使用口诀给出的计算规则进行求解,例如方程两边同时减去或除以同一个数。
- 第一组学生从口诀中学到的方法适用于求解大部分代数问题,因此在解其他方程时能够做到举一反三、触类旁通。
第三种认知负荷:关联认知负荷
- 关联认知负荷与图式构建和自动化有关,用于描述大脑将信息转化为长时记忆而付出的努力
- 如果内部认知负荷和外部认知负荷增加,那么关联认知负荷就会减轻,导致大脑很难记住曾经解决过的问题及其解决方案。
- 使用口诀的第一组学生的认知负荷并不高,因此有余力思考并记住这些口诀。
在工作中运用样例
- 与同事合作,共同组织一个代码阅读俱乐部
- 大家一起学习更容易养成定期阅读代码的习惯。
- 俱乐部的成员之间可以交换代码和解释并相互学习。
- 用好GitHub,挑选一个有所了解的仓库,然后阅读其中的代码
- 不建议选择完全陌生的仓库,至少应该对相关领域略知一二。
- 阅读探讨源代码的图书或博文
- 许多博文记录了程序员解决某个编程问题的方法,这些博文同样可以作为研究代码的资料。
10.5 小结
程序员问题解决能力的误解
- 程序员普遍认为问题解决能力是一项通用技能,实则不然
- 程序员对编程的先验知识以及待解决的编程问题都会影响解决问题的速度。
长时记忆在编程问题解决中的作用
- 长时记忆存储不同类型的记忆,这些记忆在解决问题时会起到不同的作用
- 内隐记忆(程序性记忆)和外显记忆(陈述性记忆)是两类主要的记忆。
强化与编程有关的内隐记忆的方法
- 为强化与编程有关的内隐记忆,最好能实现相关技能的自动化
- 例如熟练掌握盲打并记住相关的键盘快捷键。
强化与编程有关的外显记忆的方法
- 为强化与编程有关的外显记忆,建议研究现有的代码,最好能对照描述设计思想的代码解释进行学习。
第四部分 代码协作
心流状态与代码协作
- 心流状态有助于减轻干扰带来的负面影响
- 心流状态是一种高度集中的状态,可以让人完全沉浸在当前的任务中。
- 心流状态有助于提高代码协作的效率
- 当开发者处于心流状态时,他们的思维会更加清晰,能够更快地理解和解决问题。
大型系统的开发
- 开发便于其他团队成员处理的大型系统
- 大型系统的开发需要考虑到团队成员之间的协作,因此需要设计出易于理解和使用的系统架构。
- 介绍适岗培训流程
- 适岗培训流程可以帮助团队成员更好地理解系统的设计和使用,从而提高代码协作的效率。
第11章 编程活动和任务
程序设计包含的各种编程活动
- 深入探讨程序设计包括哪些不同的活动
- 程序设计包括需求分析、设计、编码、测试、调试、维护等活动。
促进各种编程活动的方法
- 分析如何最大限度地促进这些编程活动
- 需求分析阶段,与用户充分沟通,明确需求。
- 设计阶段,采用合适的设计方法,设计出高质量的设计方案。
- 编码阶段,遵循编码规范,编写出高质量的代码。
- 测试阶段,采用合适的测试方法,进行充分的测试。
- 调试阶段,采用合适的调试方法,快速定位并解决问题。
- 维护阶段,持续改进,提高代码质量和可维护性。
打断程序员的思路为什么会影响他们的工作效率
- 讨论打断程序员的思路会产生哪些认知影响
- 打断思路会导致程序员的工作状态被打断,需要花费额外的时间和精力重新进入工作状态。
- 打断思路可能导致程序员忘记之前的工作内容,需要重新思考和解决问题。
- 打断思路可能导致程序员产生负面情绪,影响他们的工作积极性和效率。
如何在最短的时间内找回工作状态
- 分析如何减轻干扰带来的负面影响
- 设置专注时间,避免在工作时间内频繁被打断。
- 采用番茄工作法等时间管理方法,提高工作效率。
- 学会拒绝不重要的事务,集中精力处理重要工作。
- 保持良好的工作环境和习惯,提高工作舒适度。
11.1 程序设计包括不同的编程活动
程序设计包括不同的编程活动
- 符号认知维度框架将程序设计分为5种编程活动:搜索、理解、转写、递增和探索
- 搜索是在代码库中查找特定信息的活动。
- 理解活动涉及阅读代码和执行代码以了解其功能。
- 转写活动大体围绕写代码进行。
- 递增是搜索、理解和转写的结合。
- 探索活动是运用代码来勾画问题的轮廓。
搜索活动
- 搜索活动主要与阅读代码和执行代码有关
- 程序员可能使用断点和调试工具,或是在执行代码时依靠打印语句。
- 搜索活动会影响短时记忆
- 大脑既要记住搜索的内容,也要记住已经搜索过的代码及其原因,还要记住需要进一步搜索的内容。
理解活动
- 理解活动涉及阅读代码和执行代码以了解其功能
- 程序员平均要花差不多60%的时间来理解现有的源代码。
- 理解活动还可能涉及代码测试和代码重构
- 代码测试的目的是进一步了解代码的工作原理,代码重构的目的则是提高代码可读性。
转写活动
- 转写活动大体围绕写代码进行
- 程序员针对希望为代码库添加或修改的内容制定出具体的方案,然后根据方案行事。
- 转写活动主要影响长时记忆
- 大脑必须能够回忆起代码实现的语法结构。
递增活动
- 递增是搜索、理解和转写的结合
- 代码库递增意味着增添新功能,可能既包括搜索添加代码的位置(何处添加),也包括理解现有的代码(怎样添加),然后将想法转写为实际的代码。
- 递增活动具有混合性,所以会影响全部3种记忆
- 专业程序员在工作中与各种递增任务打交道最多,因此注释和重构是改善记忆以增进理解的不二法门。
探索活动
- 探索活动是运用代码来勾画问题的轮廓
- 程序员对于计划实现的目标可能只有粗浅的认识,而编写代码有助于厘清思路,找出问题的实质以及需要用到的编程结构。
- 探索活动也要依靠其他不同的活动,所以会影响全部3种记忆,但是对工作记忆的影响尤其明显
- 编程时需要视情况调整方案和设计。
为什么符号认知维度框架不包括调试活动
- 调试活动往往涉及全部5种活动
- 调试的目的是修复错误,但修复错误前通常先要找到错误在哪里。
11.2 受到干扰的程序员
程序员受到干扰的现状和影响
- 程序员经常受到干扰,每次持续15~20分钟
- 工作期间,程序员要用差不多20%的时间来应付各种干扰。
- 程序员平均每天只有两小时能排除干扰、专心工作
- 微软公司的一项研究表明,62%的程序员认为思路被打断后再找回工作状态相当不易。
编程任务需要“预热”
- 程序理解任务中或许存在某种预热期和冷却期
- 最难的工作在两个阶段之间进行。
- 专业程序员可能发现需要这段预热期来“进入状态”
- 主动将复杂的编程任务分解为若干个子任务会有帮助。
思路被打断的后果
- 思路被打断会严重影响程序员的效率
- 受到干扰的程序员往往需要大约一刻钟才能重新开始编写代码。
- 打断正在工作的程序员会导致工作记忆丢失代码的重要信息
- 他们往往要花大力气来重建上下文,通过浏览代码库的几个位置来回忆细节,然后才能继续编写代码。
如何减轻干扰的影响
- 保存心智模型
- 把部分心智模型与代码分开存储,则有助于迅速恢复心智模型。
- 在注释中加入心智模型的解释同样有用。
- 改善前瞻性记忆
- 程序员经常在所开发的某段代码中加入“待办事项”注释,以提醒自己不要忘记写完或改进那段代码。
- Parnin还开发了一种名为“attachables”的Visual Studio插件,可以帮助思路被打断的程序员改善前瞻性记忆。
- 标记子目标
- 明确写下可以把问题分解为哪些小步骤,有助于思路被打断的程序员找回工作状态。
- 限制干扰出现的时机
- 如果思路被打断在所难免,那么把干扰限制在更合适的时候发生(例如完成一项任务后)或许有助于减轻其负面影响。
- FlowLight是一种可以放在办公桌上或显示器上方的小型LED灯,根据打字速度、鼠标点击等计算机交互行为来检测程序员是否全神贯注于工作以及大脑是否正在承受较高的认知负荷。
关于多任务处理的一些思考
- 人们无法在认知负荷较高时做到一心多用
- 我们之所以能在看书的同时做其他事情(例如听歌),是因为阅读和其他任务都已实现自动化。
- 多任务处理的相关研究
- 虽然两组学生都能很好地理解课文内容,但受到消息干扰的学生在阅读课文和回答相关问题时要多花50%左右的时间。
- 重度Facebook用户的平均成绩明显较低,这种情况在收到消息后立即回复的学生身上体现得尤其明显。
- 在写代码的同时通过Slack聊天未必有利于完成工作。
11.3 小结
程序设计中的五种编程活动及其对记忆的影响
- 搜索、理解、转写、递增和探索这5种编程活动会对不同类型的记忆造成压力
- 每种活动会对不同类型的记忆造成压力,因此应该根据具体情况采取相应的措施。
打断程序员工作的影响
- 打断正在编程的程序员不仅令人生厌,而且会影响他们的工作效率
- 程序员需要花时间重新构建代码的心智模型。
如何降低思路被打断带来的负面影响
- 通过做笔记、写文档或写注释的方式把心智模型从记忆中转移出来。
如何改善前瞻性记忆
- 如果保存心智模型无助于完成任务,那么就应该设法改善前瞻性记忆。
如何在思路被打断时减少干扰
- 尽量把干扰限制在认知负荷较低的时候发生,可以考虑使用FlowLight指示灯或手动设置Slack状态。
第12章 设计和改进大型系统
符号认知维度框架
- 符号认知维度框架是一种从认知角度分析代码库的手段
- 符号认知维度框架可以帮助程序员评估现有的大型代码库,例如“这段代码是否便于其他人修改”或“这个代码库是否便于其他人查找信息”。
利用符号认知维度框架完善现有代码库的设计
- 从认知角度而不是技术角度来分析代码库有助于程序员进一步了解团队成员如何使用自己编写的代码
- 符号认知维度框架可以帮助程序员了解代码库的可理解性、可修改性和可查找性。
代码库的属性如何影响编程活动
- 代码库的属性可以从不同的角度影响编程活动,例如代码的可读性、可修改性和可查找性
- 对于阅读代码的活动,代码的可读性尤为重要。
- 对于编写代码的活动,代码的可修改性尤为重要。
- 对于查找代码的活动,代码的可查找性尤为重要。
12.1 代码库的属性
代码库的属性
- 代码库的认知维度包括易错性、一致性、扩散性、隐藏的依赖关系、临时性、黏性、渐进式评估、角色可表达性、映射紧密性、艰难的心理操作、辅助符号、抽象和可见性
- 易错性:例如采用某些语言进行编程比采用其他语言更容易出错。
- 一致性:相似的事物能相似到哪种程度?标识符是否始终遵循同一种命名约定?代码文件的布局对不同的类来说是否相似?
- 扩散性:从第9章的讨论可知,有异味的代码不容易读懂。方法过长是一种广为人知的代码异味,因为构成方法的代码行数太多会增加理解难度。
- 隐藏的依赖关系:用于描述依赖关系对用户的可见程度。
- 临时性:用于描述使用工具时思考的难易程度。
- 黏性:与临时性有关的维度是黏性,这个维度用于描述修改给定系统的难易程度。
- 渐进式评估:与临时性有关的另一个维度是渐进式评估,这个维度用于描述在给定系统中检查或执行部分代码的难易程度。
- 角色可表达性:用于描述查看程序中不同代码元素所起的作用的难易程度。
- 映射紧密性:用于描述编程语言或代码库与待解决问题的领域的接近程度。
- 艰难的心理操作:某些系统会显著增加大脑的负担,用户需要在系统外部执行艰难的心理操作。
- 辅助符号:用于描述程序员为代码添加的额外信息,这些信息不属于形式规约。
- 抽象:用于描述系统用户能否创建与内置抽象一样强大的抽象。
- 可见性:用于描述查看系统不同部分的难易程度。
利用代码库认知维度来改进代码库
- 定期分析代码库的各个维度以及运行情况有助于保持可用性
- 理想情况下,最好定期(例如每年一次)分析代码库的维度。
设计策略及其权衡
- 应用一项设计策略(针对一个维度的调整)会导致另一个维度发生变化
- 易错性与黏性:为防止库或框架的用户犯错,程序员经常会要求用户输入额外的信息。为减少易错性,最常见的手段是允许用户为实体添加类型。如果编译器了解实体的类型,就可以利用这些信息把错误拒之门外,例如避免用户不慎将列表赋给字符串。然而,为所有实体添加类型也会在一定程度上增加用户的负担。
- 临时性和渐进式评估与易错性:如果系统的临时性较高且支持渐进式评估,那么用户就可以编写并执行不完整或不完善的代码。虽然这两种维度对于思考当前的问题有一定帮助,但用户也可能忘记删除不完整的程序或改进不完善的程序,导致代码难以理解和调试,进而影响易错性。
- 角色可表达性与扩散性:如前所述,添加诸如名称参数等额外的语法元素可以实现角色可表达性,不过额外的标签会增加代码长度。与之类似,虽然类型注释可以体现出变量所起的作用,但也会使代码库越来越大。
12.2 认知维度和编程活动
认知维度对不同编程活动的影响
- 搜索活动需要较低的扩散性和较高的辅助符号
- 隐藏的依赖关系和扩散性不利于搜索活动。
- 辅助符号如注释和变量名可以给出在哪些位置查找信息的线索。
- 理解活动需要较高的代码库可见性和角色可表达性
- 代码库的可见性较低不利于理解活动。
- 角色可表达性有利于理解活动,如果变量以及其他实体的类型和作用清晰可见,就能降低理解代码的难度。
- 转写活动需要较高的一致性,但可能会消耗更多的脑细胞
- 虽然保持一致性的代码库更容易理解,但是在实现新功能时必须确保新代码符合代码库的要求,从而消耗更多的脑细胞。
- 递增活动取决于代码库与领域的映射紧密性
- 如果代码库能使程序员集中精力思考代码的目标而不是编程概念,那么编写新代码的难度就会降低。
- 探索活动需要良好的临时性和渐进式评估,避免艰难的心理操作和抽象
- 假如系统具备良好的临时性并支持渐进式评估,就能最大限度地方便程序员在代码库中探索新的设计理念。
- 艰难的心理操作和抽象之所以不利于探索活动,是因为这两个维度会给程序员带来较高的认知负荷,导致大脑没有足够的空间思考如何解决问题。
针对预期的编程活动优化代码库
- 程序员必须了解团队成员在自己构建的代码库中最可能执行哪些操作
- 对于相对较老和稳定的库,搜索活动也许比递增活动更常见。
- 对于新的应用程序,递增活动和转写活动也许更普遍。
- 在代码库的生命周期内,可能需要应用设计策略以使代码库更符合出现概率最高的编程活动。
12.3 小结
符号认知维度框架
- 符号认知维度框架有助于程序员预测编程语言对用户的认知影响。
代码库认知维度
- 代码库认知维度是符号认知维度的扩展,可以帮助程序员评估自己构建的代码库、库或框架会对用户产生哪些影响。
权衡不同维度
- 在不同的维度之间进行权衡,维度之间可能存在此消彼长的关系。
设计策略改进代码库
- 以符号认知维度框架描述的认知维度为准绳,可以通过设计策略来改进现有代码库的设计。
不同编程活动对代码库优化的认知维度要求。
第13章 对新程序员进行适岗培训
高级程序员与初级程序员的思维方式和表现方式的区别
- 高级程序员能够更好地管理认知负荷,提高学习效率
- 资深程序员希望教会共事的编程新手管理认知负荷。
完善新程序员的适岗培训流程
- 对团队新人进行适岗培训时的各种活动
- 无论是资深程序员初次接触陌生的代码库,还是开发团队迎来新人,都需要进行适岗培训。
帮助新成员尽快融入团队的方法
- 适岗培训流程可以帮助新成员尽快融入团队
- 为此,我们首先分析高级程序员与初级程序员的思维方式和表现方式有哪些不同,然后介绍对团队新人进行适岗培训时的各种活动,最后给出帮助新成员尽快融入团队的3种方法。
13.1 适岗培训中存在的问题
适岗培训中存在的问题
- 资深程序员向新成员抛出大量新信息,导致他们被信息的洪流淹没,大脑承受了很高的认知负荷
- 培训者将代码库所涉及的领域、工作流程以及代码库本身的情况一股脑儿告诉受训者。
- 资深程序员向新成员提出一个问题或交给他们一项任务,新成员既缺乏对相关领域或编程语言进行分块的能力,也没有实现相关技能的自动化,以致大脑承受的认知负荷较高,无法完成培训者交给自己的任务
- 诸如修复一个小错误或添加一项小功能这样的任务应该是举手之劳。
- 资深程序员要求新成员同时学习太多内容,导致他们的工作记忆不堪重负
- 认知负荷代表大脑处理特定问题的能力。如果认知负荷过高,则会影响大脑进行有效的思维活动。
- 如果内部认知负荷和外部认知负荷增加,那么关联认知负荷就会减轻,以致无法记住新的信息。
- 许多资深程序员之所以不善于培训新人,是因为受到“知识的诅咒”
- 熟练掌握某种技能或知识后,程序员会不可避免地忘记当初学习这种技能或知识时遇到的困难,从而高估新成员接受新事物的能力,误以为他们可以一心多用。
- 当程序员说出“小菜一碟”的时候,可能意味着他们已经受到知识的诅咒。
13.2 高级程序员与初级程序员的区别
高级程序员与初级程序员的区别
- 高级程序员的大脑中存储着大量相关记忆,工作记忆可以从长时记忆中提取出这些信息
- 高级程序员未必能做到洞察一切,可能也需要权衡不同的方案,但他们往往对问题已有所了解,对于如何解决问题也有一定程度的认识。
- 高级程序员具备极强的代码分块能力
- 高级程序员也许扫一眼就知道某段代码的作用是清空队列,初级程序员则可能需要逐字逐句地阅读代码。
深入分析初级程序员的表现
- 新皮亚杰理论:理解程序员的思维活动
- 皮亚杰以自己的孩子为对象来设计模型,因此受到了一些学者的批评,但他的研究为新皮亚杰理论奠定了基础。
- 新皮亚杰理论对于理解初级程序员的思维活动具有重要意义,其核心思想是认知发展具备领域特殊性而不是领域普遍性。
- 学习新信息时可能会暂时忘记一些事情
- 前文讨论的4个认知发展阶段并非各自独立。学习新的编程概念或代码库的新特性时,学习者可能会在短时间内回到前一个阶段。
具体看待概念与抽象看待概念之间的区别
- 高级程序员往往采用非常笼统和抽象的术语,从不同的角度讨论某个概念
- 在解释Python的可变参数函数时,高级程序员也许会告诉不熟悉这个概念的程序员:可变参数函数支持传入数量不等的参数。
- 无论从抽象层面还是具体层面进行解释,都能帮助团队新人熟悉某门语言或某个代码库
- 根据语义波理论,学习者首先需要从宏观角度认识某个概念,包括了解它的用途以及掌握这个概念的重要性。
- 在了解概念的大致用途后,语义轮廓从波峰转向波谷,这个过程称为解包(unpacking)。
- 最后,学习者需要对概念进行抽象概括,这个过程称为重新打包(repacking)。
13.3 完善适岗培训流程
贪多嚼不烂
- 适岗培训中存在的一个问题是受训者需要执行至少4项活动,很难在不同的活动之间自如转换
- 编程活动包括搜索、理解、转写、递增和探索这5种编程活动。
- 受训者需要寻找实现某个功能的正确位置或相关信息(搜索)、学习新的源代码(理解)、深入研究代码库以增进理解(探索),以及为代码库添加新功能(递增)。
- 建议培训者从每种编程活动中专门挑选一些活动,然后要求受训者逐一完成
- 根据受训者的先验知识,培训者也可以安排不同类型的任务交替进行,例如先执行侧重于学习新编程概念的任务,再执行侧重于了解领域的任务。
改善受训者的记忆
- 培训者首先应该明白,自己觉得容易掌握的知识,受训者未必觉得容易
- 初级程序员与高级程序员的观察水平和记忆能力存在差距。
- 从3个方面完善培训流程,它们与第1章讨论的3种困惑有关
- 解释相关信息以改善长时记忆。
- 安排有针对性的小任务以改善短时记忆。
- 使用图表以改善工作记忆。
代码共读
- 团队成员一起阅读代码也是培养新人的一种手段
- 第5章介绍过以下7种文本阅读策略,它们同样可以用于代码阅读:激活、确定重要性、推断、监测、视觉化、提问、摘要。
- 运用上述7种策略进行代码共读
- 激活:共读环节开始前,请盘点代码中涉及的相关概念。
- 确定重要性:假如对某个领域知之甚少,就很难区分哪些是核心知识,哪些是次要知识。
- 推断:补全没有明确声明的细节同样很困难。
- 监测:培训者在适岗培训期间最重要的任务是随时掌握受训者的理解水平。
- 视觉化:根据受训者的水平,培训者既可以自己绘制图表以帮助他们阅读代码,也可以要求受训者绘制图表以加深对代码的理解。
- 提问:代码共读应该纳入定期问答环节。
- 摘要:在适岗培训期间,代码共读的最后一步是为一起读过的代码编写摘要。
13.4 小结
高级程序员与初级程序员的思维方式和表现方式的区别
- 高级程序员可以对代码进行抽象推理,无须查阅代码就能进行思考。
- 初级程序员则往往将注意力集中在代码的具体性质上,很难摆脱细节的束缚。
学习新信息时,中级程序员的思维有时会“退化”到初级程序员的水平。
程序员学习新概念的方式
- 程序员需要通过抽象术语和具体示例来学习新概念。
- 程序员也需要时间将新概念与先验知识联系起来。
适岗培训期间的建议
- 一次只安排受训者执行一项编程活动。
- 适岗培训开始前,请准备好有助于改善受训者的长时记忆、短时记忆和工作记忆的相关资料。
写在最后
本书的创作背景与收获
- 撰写本书使作者对认知科学和程序设计的研究更加深入
- 在深入研究认知科学后,作者不再过分苛求自己。
- 写作带给了作者一个启示,那就是困惑和认知负荷在生活和学习中随处可见,所以不必过分担心。
本书与编程语言Hedy的关系
- 编程语言Hedy的开发工作与本书的创作过程密不可分
- 本书中介绍的许多内容(例如减少认知负荷、错误概念、语义波和间隔重复)在代码实现上都是采用Hedy。
本书对读者的建议与推荐
- 推荐了几本与本书主题相关的书籍和科学家
- 《思考,快与慢》一书从更广泛的层面剖析了大脑的工作机制。
- 《如何学习》一书深入探讨了间隔重复和记忆的话题。
- 《人脑如何学数学》一书详细剖析了数学教学和抽象领域的研究成果。
- 《A Philosophy of Software Design》是编程领域一本不可多得的好书。
- 《Software Design Decoded》一书归纳出了编程专家的66种思维方式。
- 推荐了两篇对作者思考产生极大影响的科学论文
- “Why Minimal Guidance During Instruction Does Not Work”彻底颠覆了作者对教学的认知。
- “Toward a Developmental Epistemology of Computer Programming”使作者意识到自己在编程教学方面存在的不足,并领悟到如何改进教学方法。
本书介绍的优秀科学家及其工作
- Sarah Fakhoury、Alexander Serebrenik、Chris Parnin、Janet Siegmund、Brittany Johnson、Titus Barik、David Shepherd以及Amy J. Ko等人的工作。