软件开发人员花费 35-50% 的时间来验证和调试软件。1 调试、测试和验证的成本估计占软件开发项目总预算的 50-75%,每年超过 1000 亿美元。11 虽然工具、语言和环境减少了在单个调试任务上花费的时间,但它们并没有显著减少调试的总时间,也没有降低调试的成本。因此,过度关注在开发过程中消除错误是适得其反的;程序员应该将调试视为一种解决问题的练习。
程序员更应该专注于获取和鼓励有效的学习策略,以减少调试时间,并改变他们看待挑战的方式。本文介绍了如何通过应用斯坦福大学 Carol Dweck 和其他人对问题解决心理学的研究,来学习、教授和指导有效的问题解决技能。
在 1974 年的经典著作《编程风格的要素》中,Kernighan 和 Plauger 写道:“每个人都知道调试比一开始编写程序要难两倍。因此,如果你在编写程序时已经用尽了你的聪明才智,你又将如何调试它呢?”6 调试真的比编写程序难两倍吗?如果是这样,为什么会这样呢?
为了回答这些问题,我们首先考虑一下错误是如何产生的。在 2005 年的一篇论文中,Andrew J. Ko 和 Brad A. Meyers 认为,错误的产生是“在编程活动过程中形成的认知崩溃链”的结果。7
从根本上讲,所有软件都描述了系统状态随时间的变化。由于软件中的状态和状态转换的数量可能具有组合复杂性,程序员在开发过程中必然依赖于系统行为的近似值(称为心智模型)。心智模型的目的是让程序员能够准确地推理系统行为。
由于心智模型是近似值,因此有时是不正确的,当软件在错误的假设之上开发时,会导致软件中出现异常行为。新手程序员在更大程度上经历了这个问题:他们对语言和开发环境的模型必然是不完整的,即使是一个简单的语法错误也可能成为学习编程的人的主要障碍。这不仅限于新手程序员。例如,C11 语言规范就超过 700 页。有多少系统程序员完全理解这门语言呢?
最阴险的错误发生在程序员错误地认为他们的心智模型是完整的时候。这就是问题的症结所在:他们假定他们的实现是正确的,并且根据定义,他们不知道自己哪里出错了。程序员解决此类错误的唯一希望是通过知识获取。虽然 Kernighan 关于编程和调试之间难度量化差异的说法是否正确尚不清楚,但调试似乎确实是更困难的任务。由于解决错误需要学习,因此通过更好地理解有效的学习和教学策略,可以使调试过程变得更容易。
调试只是问题解决的特定领域术语。错误被描述为文字问题:例如,“当条件 A 和 B 都为真时,对对象 O 的越界写入会导致相邻内存损坏。” 不幸的是,文字问题被发现是最难教授的问题之一,而在软件中,几乎所有错误都可以有效地归类为文字问题。13
也许是为了弥补这种困难,许多现有的调试教学研究首先侧重于区分“专家”和“新手”,并评估每种人在调试任务中使用的技术。然后,它试图通过教授专家技术来提高新手的能力。然而,对文献的回顾发现,即使是专家在调试技能方面也存在很大差异。8
最有效的调试人员借鉴了丰富的经验以及精湛的问题解决技能。他们还采用通用的问题解决策略13,而不是将每个单独的错误都视为一个新的特定案例。虽然教授专家程序员的技术可以减少新手在编程活动上花费的时间,但尚未证明它能有效减少调试时间。此外,对接受过和未接受过专家技术干预的小组的学生进行测试,在测试分数上并未产生统计学上的显著差异。2
经验和准确的模型正是新手程序员所缺乏的工具。尽管计算机科学教育花费大量时间教授算法和基础知识,但似乎并没有花费太多时间将它们应用于一般问题。调试在大学里没有作为一门专门的课程来教授。尽管几十年来一直有文献建议开设此类课程,但仍然没有强大的调试教学模型。
问题在于教什么和如何教。仔细考虑心理学领域的研究有助于理解学生的需求和设计合适的课程。13 因此,教师、导师和教育工作者应该了解该领域的相关研究,以指导他们教什么和如何教。个人必须认识到如何处理问题,以及问题是被视为能力限制还是学习过程的一部分。
在开发旨在减少或消除错误的工具、语言和编程环境方面已经付出了巨大的努力。工具(一旦理解其使用方法)无疑可以节省调试过程中的时间,但它们无法解决所有问题。如果特定问题没有工具怎么办?如果现有工具无法扩展到重现问题所需的负载怎么办?如果你不知道这些工具,或者无法负担得起它们怎么办?工具不是万能药;当它们不存在(或以其他方式无法使用)时,你需要愿意并且能够编写它们或放弃使用它们。
基于虚拟机的语言、解释型语言和具有运行时环境的语言鼓励用户将执行环境视为黑匣子。这里的目标是通过减少程序员必须维护的心智模型的范围来使编程更容易。当这些执行环境中发生错误时,你就会完全不理解。你或许可以通过更多地了解执行环境来解决这个问题,但那么这种抽象的意义何在呢?此类运行时环境不是万能药;你仍然需要了解它们的行为方式。
最后,在形式可验证语言领域存在广泛的研究。这些环境允许程序员在解决某些问题的同时证明其代码的正确性,但它们并不能帮助我们理解他们首先要解决的问题。如果你不完全理解一个问题,然后基于不完整的理解实现一个“可证明正确的”解决方案,那么仍然需要调试。此类语言不是万能药;你仍然需要在编写软件之前完全理解要解决的问题。
由于错误的发生是由于不完整的心智模型造成的,因此完全依赖工具、运行时环境和语言来捕获错误的理念似乎是适得其反的。这几乎就像我们在说我们不可能足够聪明来理解这些错误,所以何必费心呢?这种观点完全站在 Carol Dweck 关于“智力自我理论”的研究中描述的“实体理论家”一边。5
Dweck 是动机领域的一位领先研究员,她负责进行了四十年的研究,旨在识别和描述高成就人士的行为。她的工作在不同文化、性别、年龄和社会经济背景的个体中得到了多次验证。
Dweck 提出,个体在自我理论的谱系中处于某个位置。这个谱系从一端的实体理论家到另一端的渐进理论家不等。实体理论家倾向于将智力视为天生的和固定的,并且从根本上认为在提高智力方面无能为力。渐进理论家从根本上认为,具有挑战性的问题是学习过程的核心部分,并且智力是可塑的:它可以通过努力工作来获得。当面对挑战时,实体理论家将其解释为他们能力的限制,并且不会努力解决它们。在文献中,实体理论经常被称为固定型思维模式,而渐进理论被称为成长型思维模式——这在很大程度上归因于实体理论家和渐进理论家的内在动机。
尽管 Dweck 的工作最初侧重于智力理论,但这些自我理论可以应用于特定领域的信念。例如,个体可以对智力持有渐进理论,同时仍然对编程或调试等特定技能持有实体理论观点12,因此这项研究并不意味着全球性的观点。
具有智力实体理论的个体更可能被外表而非表现所驱动,更可能在面对问题时从事适应不良的行为(例如放弃或作弊),并且不太可能与他人合作。在软件开发领域,这些行为通过适应不良的策略表现出来,包括英雄崇拜、冒名顶替综合征、“货物崇拜”和散漫的工作态度。
相比之下,渐进理论家受以绩效为导向的目标驱动。实体理论家采取捷径来显得聪明(例如堆砌大量客观上简单的任务),而渐进理论家则正面应对难题,将努力工作视为获取知识和交付价值的必要条件。此外,渐进理论家倾向于在团队中有效地工作,重视帮助教育和促进他人的成功,并从事其他通常对专业编程环境有利的行为。
这种智力的渐进理论显然对程序员有利,并且是该领域最优秀人才的特征,无论他们是专业人士还是学者。弄清楚如何有效地应用 Dweck 的研究将是有益的,认识到编程实践(像许多其他领域一样)受益于终身学习。教育工作者、管理者和导师将从将这项研究纳入课程、管理和教学策略中受益。
将 Dweck 的研究应用于我们领域的关键在于,有可能在自我理论的谱系上“转变”。通过改变你对人们的反应、回应和赞扬方式,你可以帮助他人(以及你自己)从“固定”型思维模式转变为“成长”型思维模式。
与你刚开始时相比,你现在的编程能力如何?调试能力如何?无论你从事编程工作一年还是 50 年,你几乎肯定都在解决在你开始学习之旅时似乎棘手的问题。这不是天生能力的结果:在我们开始编程之前,我们谁都没有这种能力。我们不必追溯到很久以前的历史就能理解,这个领域的能力在人类中还处于萌芽状态。
尽管如此,在将心理学研究应用于我们的工作13之前,我们必须仔细考虑它,并决定它是否相关。在 2008 年的一篇论文中,Laurie Murphy 和 Lynda Thomas 列举了拥抱此类研究可以在计算机科学领域提供帮助的方式:9
• 将这项研究应用于计算机科学入门课程可能会提高学生的学习效果和课程保留率。
• 计算机科学教育和实践中的性别差距可能直接归因于文化对女性智力自我理论的影响。
• 一些研究发现,协作工作(例如结对编程)非常有效,而在另一些研究中则非常无效。实体理论家与渐进理论家之间目标冲突可以解释这些明显的矛盾。
• 防御性的课堂氛围,其特点是学生提出“伪问题”来展示知识,而教授则给予这些学生特殊的地位,可以通过采纳 Dweck 的工作来解释和改善。
由于错误以问题的形式出现,并且由于实体理论家倾向于将问题视为能力的根本限制,因此这里的重点应该是引导学生、同伴和同事朝着更可塑的智力观迈进。
无论是作为管理者、导师还是教育工作者,采纳和推广可塑的自我理论对于培养成功的学生和同事都非常重要。调试绝不能是教育中的事后补救措施;行业必须停止坚持将错误解释为个别程序员的失败(尤其是在个别程序员很少负责整个系统的设计和功能的情况下)。相反,应该赞扬程序员在解决错误方面所做的努力。在所有情况下,解决错误都是学习过程的一部分。如何将它们呈现为学习过程的一部分呢?
一些研究试图将个体的观点从固定型思维模式转变为可塑型思维模式。熟悉相关文献在这里很重要,尤其是对于教育工作者和管理者而言。在 2004 年的一篇论文中,Mantz Yorke 和 Peter Knight 建议教育工作者应该“(1)认识到自我理论对学生学习的重要性;(2)能够推断学生是倾向于固定型思维模式还是可塑型思维模式;以及(3)拥有鼓励‘固定’型学生转向可塑型思维模式的策略。”14
为了做到这一点,Murphy 和 Thomas 建议研究心理学家 Lev Vygotsky 在 20 世纪早期的工作。他们指出,“当学生被稍微推过他们独立能力时,他们学得最好。” 这需要一位能够帮助学生超越他们当前能力的教师或导师;Yorke 和 Knight 观察到,当教师和学生都具有可塑的智力观时,这个系统效果最佳。
将个体从实体型思维模式转变为渐进型思维模式可以像以特定方式构建信息一样简单。在多项研究中,Dweck 指出,以赞扬能力的方式呈现的信息会促进实体理论的形成,而赞扬努力则会促进渐进理论的形成。无论接受信息的人是否是被赞扬的人,这似乎都是正确的。
Dweck 表示,基于赞扬的转变的时间效应尚不清楚。在 Cutts 等人研究的一组个体中,每次对评分作业给出反馈时,他们都会始终如一地强化渐进型思维模式。3 只有接受这种干预(以及另外两种干预方法)的学生才看到了向渐进型自我理论的转变以及测试分数在统计学上的显著提高(这是我发现的任何其他研究都未能证明的壮举)。持续的环境反馈促进渐进理论可能足以让个体长期采纳。鉴于它已被证明在短期情况下有效,持续的反馈可能只是一个功能等价物。
大多数此类反馈的例子都属于以促进以成长为导向的目标的方式构建信息。当一位同事解决了一个特别棘手的错误时,人们倾向于说:“你真聪明!” 相反,他们应该说:“你工作真棒!” 如果你给工程师分配对他们来说非常容易解决的任务,你可以表示歉意:“很抱歉给你分配了一个你学不到太多东西的任务。” 尝试在未来给这些同事分配更具挑战性的工作。学生和个人在解决错误时会感到沮丧。如果你、你的学生或你的同事表达了沮丧,请尝试从你或他们期望在完成项目后学到的东西的角度来构建它。
不幸的是,学生和求职者通常无法选择持有这种观点的教师或管理者。由于当学生和教师都持有渐进理论时,学习效果最佳14,因此组织聘请具有这种观点的个体并对现有员工进行相关材料的培训至关重要。
持有实体理论的教师和导师更可能只专注于帮助他们认为最聪明的学生,而将苦苦挣扎的学生视为无望的案例。这尤其不幸,因为在多项研究中,Dweck 表明,在从小学过渡到中学或从高中过渡到大学的过程中,具有可塑型思维模式的成绩不佳的学生倾向于跑赢具有实体理论的高成就学生。具有实体理论的教师不太可能帮助成绩不佳的学生取得成功。
教育是一个终身过程。作为个体,我们应该在日常生活中采纳可塑的智力观。有一些特定的方法来处理问题,这些方法可以发展和强化可塑的智力观。
人们可能迫使自己进入实体框架思维的一种方式是过度依赖参考手册和文档。memcpy 的参数是按源、目标、长度的顺序排列的吗?还是按目标、源、长度的顺序排列的?strstr 是 haystack, needle 吗?还是 needle, haystack?许多人养成了在问题出现时立即查阅参考资料(例如系统手册)的习惯。
主动回忆是一种学习方法,你首先在查找答案之前进行猜测。(这是抽认卡等学习工具的基础,但必须正确使用才能有效。)对于 memcpy 和 strstr 等接口,首先编写代码:尽你所能猜测参数顺序。完成此操作后,查看参考手册以确认你是否正确完成。
想想你上次花一整天时间进行马拉松式调试会话是什么时候。你解决问题了吗?还是你需要休息一下,做一些其他的事情,也许回家睡觉,然后在第二天解决问题?
工程师在调试时经常陷入沉没成本谬误,尽管花费的时间回报递减,但仍花费额外的精力来解决问题。我们的大脑不适合连续数小时专注于特定任务。分段学习的想法是在一项活动过程中切换到一两个额外的且不相关的任务。切换思路和休息(也许违反直觉)是在你遇到困难时取得进展的有效方法。
对于管理者、导师和教育工作者来说,情况也是如此。明确允许同事和学生有时间处理其他问题。学生和员工通常不会要求处理其他事情,因此至关重要的是给予人们处理信息所需的时间。
分段学习并不意味着在事情变得困难时放弃。你必须坚持不懈:毅力和热情至少与智力对于成功和技能发展同等重要。一个领域中最成功的人在他们能力边缘练习他们的技能多年。这种“毅力”与个人成功因素高度相关。4
随着经验的积累,指导他人变得合适。在教导和指导他人时,保持好奇心至关重要。到达某些知识存在许多路径,你自己的内在观点并不总是对每个人都最好。在学习时,人们希望将新知识与现有知识联系起来;这为他们提供了坚实的基础。好奇心是关于对不一定反映你自身观点的想法、解决方案和思维方式持开放态度。你必须从你希望教育的人的角度进行教学。
通过持续学习、对问题的可塑型看法以及有效使用工具,你可以在调试方面取得成功。尽管如此,有些人仍然坚持认为调试与其说是一门科学,不如说是一门艺术。我认为我们可以完全摒弃这种观点。很明显,调试需要学习,而科学方法是专门为产生新知识而设计的。该方法总结如下:(1)制定关于问题的一般理论。(2)提出导致假设的问题。(3)形成假设。(4)收集和测试数据以验证假设。(5)重复。
以我的经验,很少有人关注假设的形成,导致浪费精力,在没有任何关于错误原因的理论的情况下进行测试。形成一个好的假设比看起来要困难得多。在实践中,假设形成得很差,并且许多假设更多地依赖于直觉而非信息收集。直觉可能是调试的有效策略,但需要丰富的经验,并且当仅用作唯一策略时,会让程序员在处理新的、不熟悉的错误时措手不及。缺乏解决这些错误的框架对于实体理论家来说尤其具有破坏性。
一个好的假设描述了一个问题,并且是可测试的和可证伪的。事实上,形成一个正确的假设几乎总是意味着一个错误已被完全理解。考虑以下三个陈述
• 日志模块中存在错误。
• 当并发日志生产者将同一项目排队时,日志模块中存在竞争条件。
• 并发日志生产者消耗池化工作对象之间的竞争条件是由于当池为空时处理不当的返回代码引起的,并且稍后当消费者尝试将同一对象多次重新排队到池中时,由于池已满而失败,从而导致双重释放。
这些陈述是围绕解决一个真实错误的思想过程的一部分。第一个假设表明几乎没有计划或研究,并且是程序员对可能构成错误的“直觉”的结果。这是一个可测试的假设,但它很糟糕:如果通过测试证实了这个假设,则测试无法提供任何关于如何解决问题的更多数据。
第二个陈述略好一些。很明显,它在更多信息的基础上运行,因此看起来错误此时已被重现。这个假设仍然是不完整的,因为它没有对为什么并发日志生产者会产生相同的项目做出任何预测。此外,尽管它听起来像是描述了故障是什么(竞争条件),但这实际上并不是最终的缺陷,如第三个假设中所述。
第三个假设显然是最好的。它描述了错误发生的原因和故障是什么。重要的是,它指出了故障的原因与程序实际发生故障的位置和时间是分开发生的。这个假设非常棒,因为它可以非常具体地进行测试。如果回归测试是你开发框架的一部分,那么只有这个假设提供了关于此类测试应如何表现的描述。
可证伪性是真实假设的一个重要且至关重要的属性。如果一个假设无法被证明是错误的,那么任何测试都会证实它。这不可能让你确信你理解了这个问题。
形成合理的假设对于其他原因也很重要。心智模型可以用来直观地了解某些错误的原因,但对于更困难的问题,依靠心智模型来描述问题是完全错误的:心智模型是不正确的,这就是错误发生的原因。抛弃心智模型对于形成合理的假设至关重要。
这可能比看起来更难。例如,代码中涉嫌包含错误的注释可能会强化现有的心智模型。这可能会导致你掩盖有错误的代码,认为它显然是正确的。例如,考虑一下
/* 刷新所有日志条目 */
for (i = 0; i <= n_entries; i++) { flush_entry(&entry[i]); }
这段代码(也许很明显)说明了一个差一错误的例子。上面的注释是正确的,但不完整。这段代码将刷新所有条目。它还会多刷新一个。调试时,将注释视为仅供参考,而不是规范。
调试是应用计算机科学中最困难的方面之一。通过 Carol Dweck 和其他人进行的意义深远的研究,人们对个体在问题解决领域的观点和动机有了更好的理解。这项研究为促进学生、同事和你自己的持续成长提供了一种手段。
调试是一门科学,而不是一门艺术。为此,高等教育机构应该像对待科学一样对待调试。这些机构是时候开设专门讨论调试的完整课程了。早在 1989 年就有人提出这种需求。10 2004 年,Ryan Chmiel 和 Michael C. Loui 观察到,“美国计算机协会和 IEEE 计算机协会提出的计算课程很少提及调试的重要性。”2 这似乎仍然是正确的。
仅通过经验学习(Oman 等人认为这是学习调试技能的主要方式10)是令人沮丧且代价高昂的。在软件工程行业人员不足的时代,似乎某些受社会和文化影响而产生自我理论的个体被落在了后面。理解 Dweck 的工作并改变我们进行教育、指导和个人学习习惯的方式,可能会对软件开发行业的进步产生深远的长期影响。虽然继续研究简化调试任务的工具非常重要,但我们也必须拥抱并继续研究,询问并展示如何更好地帮助学生、同事和同伴在计算机科学领域取得成功。
1. Britton, T., Jeng, L., Carver, G., Cheak, P., Katzenellenbogen, T. 2013. 可逆调试软件。剑桥贾奇商学院; http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.444.9094&rep=rep1&type=pdf。
2. Chmiel, R., Loui, M. C. 2004. 调试:从新手到专家。SIGCSE Bulletin 36(1): 17-21。
3. Cutts, Q., Cutts, E., Draper, S., O'Donnell, P., Saffrey, P. 2010. 操纵思维模式以积极影响入门编程绩效。第 41 届 计算机科学教育技术研讨会论文集:431-435。
4. Duckworth, A. L., Peterson, C., Matthews, M. D., Kelly, D. R. 2007. 毅力:对长期目标的坚持和热情。人格与社会心理学杂志 92(6): 1087-1101。
5. Dweck, C. 1999. 自我理论:它们在动机、人格和发展中的作用。心理学出版社。
6. Kernighan, B. W., Plauger, P. J. 1974. 编程风格的要素。麦格劳-希尔。
7. Ko, A. J., Meyers, B. A. 2005. 研究编程系统中软件错误原因的框架和方法。视觉语言和计算杂志 16(1-2): 41-84。
8. McCauley, R., Fitzgerald, S., Lewandowski, G., Murphy, L., Simon, B., Thomas, L., Zander, C. 2008. 调试:从教育角度对文献的回顾。计算机科学教育 18(2): 67-92。
9. Murphy, L., Thomas, L. 2008. 固定型思维模式的危险:自我理论研究对计算机科学教育的影响。SIGCSE Bulletin 40(3): 271-275。
10. Oman, P. W., Cook, C. R., Nanja, M. 1989. 编程经验对调试语义错误的影响。系统与软件杂志 9(3): 197-207。
11. RTI. 2002. 软件测试基础设施不足的经济影响; http://www.nist.gov/director/planning/upload/report02-3.pdf。
12. Scott, M, Ghinea, G. 2014. 思维模式的领域特异性:能力信念与编程实践之间的关系。IEEE 教育汇刊 57(3): 169-174。
13. Winslow, L. 1996. 编程教学法——心理学概述。SIGCSE Bulletin 28(3): 17-22。
14. Yorke, M., Knight, P. 2004. 自我理论:对高等教育教学和学习的一些影响。高等教育研究 29(1): 25-37。
本科软件工程
满足专业软件开发的需求
Michael J. Lutz, J. Fernando Naveda, 和 James R. Vallino
https://queue.org.cn/detail.cfm?id=2653382
智能编码:人与工具
工具可以帮助开发人员提高生产力,但它们不能替代思考。
Donn M. Seeley
https://queue.org.cn/detail.cfm?id=945135
面试技巧
区分优秀的程序员和糟糕的程序员
Kode Vicious
https://queue.org.cn/detail.cfm?id=1998475
Devon H. O'Dell 是 Fastly 的技术主管,他的主要工作包括指导团队成员以及 Fastly 核心缓存基础设施的可扩展性、功能性和稳定性。在加入 Fastly 之前,O'Dell 曾担任 Message Systems 的首席软件架构师,并在 Momentum 高性能消息传递平台方面做出了重要贡献。他在过去 15 年的经验涵盖了从 Web 应用程序到嵌入式系统固件(以及两者之间的大多数领域)。如今,你通常可以发现他正在调试并对某些事情感到困惑。
版权所有 © 2017 归所有者/作者所有。出版权已许可给 。
最初发表于 Queue 第 15 卷,第 1 期—
在 数字图书馆 中评论本文
Charisma Chan, Beth Cooper - 调试 Google 分布式系统中的事件
本文介绍了 2019 年对 Google 工程师如何调试生产问题进行的研究结果,包括工程师使用不同组合有效调试的工具类型、高级策略和低级任务。它检查了用于捕获数据的研究方法,总结了生产调查的常见工程历程,并分享了专家如何调试复杂分布式系统的示例。最后,本文将这项研究的 Google 具体内容扩展到提供一些可以在你的组织中应用的实用策略。
Peter Phillips - 使用跟踪增强调试
创建用于运行旧程序的模拟器是一项艰巨的任务。你需要透彻理解目标硬件以及模拟器要执行的原始程序的正确功能。除了功能正确之外,模拟器还必须达到以原始实时速度运行程序的目标性能。实现这些目标不可避免地需要大量的调试。这些错误通常是模拟器本身中的细微错误,但也可能是对目标硬件的误解或原始程序中实际已知的错误。(原始程序的二进制数据也可能已轻微损坏或不是预期的版本。)
Queue Readers - 又一天,又一个错误
作为本期关于程序员工具的一部分,我们在 Queue 决定就调试主题进行一次非正式的网络民意调查。我们请你告诉我们你使用的工具以及你如何使用它们。我们还收集了一些关于那些难以追踪的错误的故事,这些错误有时让我们想从事其他职业。