如何高效调试代码

文章详细介绍了如何高效地调试代码,包括理解程序错误的分类(语法错误、运行错误和语义错误),使用调试技巧如交互接口分析、错误信息解析、增量式开发、思维跳跃、对分调试,以及提高调试效率的方法。此外,还提供了处理常见问题的FAQ,帮助开发者更好地定位和修复bug。
摘要由CSDN通过智能技术生成

目录

如何高效调试代码

1、程序错误分类

2、调试与编程

3、交互接口与函数

4、错误信息分析

5、增量式开发

6、思维跳跃

           7、对分调试

           8、提高调试效率

           9、如何找到bug

FAQ

A.1 语法错误

A.1.1 我不断地改代码,但似乎一点用都没有。

A.2 运行时错误

A.2.1 我的程序什么也没有做。

A.2.2 我的程序挂死了。

A.2.3  执行流

A.2.4 我加入了太多的打印语句以至于输出刷屏。

A.3 语义错误

A.3.1 我的程序不能工作。

A.3.2 我写了一个超大的密密麻麻的表达式,结果它运行得不正确。

A.3.3 我真的是没办法了,我需要帮助。

A.3.4我不干了,我真的需要帮助。

注:本文选择性摘自《Think Python》 Allen Downey著


如何高效调试代码

1、程序错误分类

程序一般会有三种错误:语法错误,运行错误和语义错误。区分这三种错误有助于更快速地追踪错误。

  • 语法错误Syntax error:

语法是指程序的结构和规则。比如括号要成对用。如果你的程序有某个地方出现了语法错误,Python会显示出错信息并退出,程序就不能运行了。最开始学习编程的这段时间,你遇到的最常见的估计就是这种情况。等你经验多了,基本就犯的少了,而且也很容易发现了。

  • 运行错误Runtime error:

第二种错误就是运行错误,显而易见了,就是直到运行的时候才会出现的错误。这种错误也被叫做异常,因为一般表示一些意外的尤其是比较糟糕的情况发生了。

  • 语义错误Semantic error:

第三种就是语义错误,顾名思义,是跟意义相关。这种错误是指你的程序运行没问题,也不产生错误信息,但不能正确工作。程序可能做一些和设计目的不同的事情。发现语义错误特别不容易,需要你仔细回顾代码和程序输出,要搞清楚到底程序做了什么。

2、调试与编程

给程序调试是你应当掌握的最关键技能之一了。尽管调试的过程会有挫败感,也依然是最满足智力,最有挑战性,也是编程过程中最有趣的一个项目了。

某种程度上,调试像是侦探工作一样。你面对着很多线索,必须推断出导致当前结果的整个过程和事件。

调试也有点像一门实验科学。一旦你有了一个关于所出现的错误的想法,你就修改一下程序再试试看。如果你的假设是正确的,你就能够预料到修改导致的结果,这样在编程的水平上,你就上了一层台阶了,距离让程序工作起来也更近了。

如果你的推测是错误的,你必须提出新的来。就像夏洛克.福尔摩斯之处的那样,『当你剔除了所有那些不可能,剩下的无论多么荒谬,都必然是真相。』(引自柯南道尔的小说《福尔摩斯探案:四签名》)

对于一些人来说,编程和调试是一回事。也就是说,编程就是对一个程序逐渐进行调试,一直到程序按照设想工作为止。这种思想意味着你要从一段能工作的程序来起步,一点点做小修改和调试。

例如,Linux是一个有上百万行代码的操作系统,但最早它起源于Linus Torvalsd的一段小代码。这个小程序是作者用来探索Intel的80386芯片的。根据Larry Greenfield回忆,『Linus早起的项目就是很小的一个程序,这个程序能够在输出AAAA和BBBB之间进行转换。这后来就发展除了Linux了。』(引用自Linux用户参考手册beta1版)

3、交互接口与函数

一个交互接口,就像是函数和调用者的一个中间人。调用者提供特定的参数,函数完成特定的任务。

例如,polyline这个多段线函数,需要四个实际参数:t必须是一个Turtle小乌龟;n(边数)必须是一个整形;length(长度)应该是一个正数;angle(角度)必须是一个以度为单位的角度值。

这些要求叫做『前置条件』,因为要在函数开始运行之前就要实现才行。相应的在函数的结尾那里的条件叫『后置条件』。后置条件包含函数的预期效果(如画线段)和其他作用(如移动海龟或进行其他改动)。

前置条件是准备给函数调用者的。如果调用者违背了(妥当标注的)前置条件,然后函数不能正常工作,这个bug就会反馈在函数调用者上,而不是函数本身。

如果前置条件得到了满足,而后置条件未能满足,这个bug就是函数的了。所以如果你的前后置条件都弄清晰,对调试很有帮助。

4、错误信息分析

当语法错误或者运行错误出现的时候,错误信息会包含很多有用的信息,不过信息量太大,太繁杂。最有用的也就下面这两类:

  • 错误的类型是什么,以及

  • 错误的位置在哪里。

    >>> x = 5
    >>>  y = 6
        File "<stdin>", line 1
          y = 6
          ^
    IndentationError: unexpected indent

    这个例子里面,错误的地方是第二行开头用一个空格来缩进了。但这个错误是指向y的,这就有点误导了。一般情况下,错误信息都会表示出发现问题的位置,但具体的错误可能是在此位置之前的代码引起的,有的时候甚至是前一行。

    同样情况也发生在运行错误的情况下。假设你试着用分贝为单位来计算信噪比

    import math
    signal_power = 9
    noise_power = 10
    ratio = signal_power // noise_power
    decibels = 10 * math.log10(ratio) print(decibels)

    运行这个程序,你就会得到如下错误信息:

    Traceback (most recent call last):
        File "snr.py", line 5, in ?
            decibels = 10 * math.log10(ratio)
    ValueError: math domain error

    这个错误信息提示第五行,但那一行实际上并没有错。要找到真正的错误,就要输出一下ratio的值来看一下,结果发现是0了。那问题实际是在第四行,应该用浮点除法,结果多打了一个右斜杠,弄成了地板除法,才导致的错误。

    所以你得花点时间仔细阅读错误信息,但不要轻易就认为出错信息说的内容都是完全正确可靠的。

5、增量式开发

写一些复杂函数的时候,你会发现要花很多时间调试。

要应对越来越复杂的程序,你不妨来试试增量式开发的办法。增量式开发的目的是避免长时间的调试过程,一点点对已有的小规模代码进行增补和测试。

你动手的时候,每次建议只添加一两行代码。等你经验更多了,你发现自己可能就能够驾驭大块代码了。不论如何,增量式开发总还是能帮你节省很多调试消耗的时间。

这个过程的核心如下:

  1. 一定要用一个能工作的程序来开始,每次逐渐添加一些细小增补。在任何时候遇到错误,都应该弄明白错误的位置。

  2. 用一些变量来存储中间值,这样你可以显示一下这些值,来检查一下。

  3. 程序一旦能工作了,你就应该把一些发挥『脚手架作用』的代码删掉,并且把重复的语句改写成精简版本,但尽量别让程序变得难以阅读。

把大的程序切分成小块的函数,就自然为我们调试建立了一个个的检查点。在不工作的函数里面,有几种导致错误的可能:

  • 函数接收的参数可能有错,前置条件没有满足。

  • 函数本身可能有错,后置条件没有满足。

  • 返回值或者返回值使用的方法可能有错。

要去除第一种情况,你要在函数开头的时候添加一个print语句,来输出一下参数的值(最好加上类型)。或者你可以写一份代码来明确检查一下前置条件是否满足。

如果形式参数看上去没问题,在每一个返回语句之前都加上print语句,显示一下要返回的值。如果可以的话,尽量亲自去检查一下这些结果,自己算一算。尽量调用有值的函数,这样检查结果更方便些。

如果函数看着没啥问题,就检查一下函数的调用,来检查一下返回值是不是有用到,确保返回值被正确使用。

在函数的开头结尾添加输出语句,能够确保整个执行流程更加可视化。如果你对执行流程比较困惑,这种输出会有一定帮助。有效率地进行脚手架开发是需要时间的,但稍微利用一下这种思路,反而能够节省调试用的时间。

6、思维跳跃

跟随着运行流程是阅读程序的一种方法,但很快就容易弄糊涂。另外一个方法,我称之为『思维跳跃』。当你遇到一个函数调用的时候,你不用去追踪具体的执行流程,而是假设这个函数工作正常并且返回正确的结果。

实际上你已经联系过这种思维跳跃了,就在你使用内置函数的时候。当你调用math.cos或者math.exp的时候,你并没有仔细查看这些函数的函数体。你就假设他们都工作,因为写这些内置函数的人都是很可靠的编程人员。

你调用自己写的函数时候也是同样道理。比如在6.4部分,我们谢了这个叫做is_divisible的函数来判断一个数能否被另外一个数整除。一旦我们通过分析代码和做测试来确定了这个函数没有问题,我们就可以直接使用这个函数了,不用去理会函数体内部细节了。

对于递归函数而言也是同样道理。当你进行递归调用的时候,并不用追踪执行流程,你只需要假设递归调用正常工作,返回正确结果,然后你可以问问自己:『假设我能算出来n-1的阶乘,我能否计算出n的阶乘呢?』很显然你是可以的,乘以n就可以了。

当然了,当你还没写完一个函数的时候就假设它正常工作确实有点奇怪,不过这也是我们称之为『思维飞跃』的原因了,你总得飞跃一下。

7、对分调试

当你开始写更为复杂的程序时,你会发现大部分时间都花费在调试上。更多的代码意味
着更高的出错概率,并且会有更多隐藏 bug 的地方。
减少调试时间的一个方法就是“对分调试”。例如,如果程序有 100 行,你一次检查一行,
就需要 100 步。
相反,试着将问题拆为两半。在代码中间部分或者附近的地方,寻找一个可以检查的中
间值。加上一行 print 语句 (或是其他具有可验证效果的代码),然后运行程序。
如果中间点检查出错了,那么就说明程序的前半部分存在问题。如果没问题,则说明是
后半部分出错了。
每次你都这样检查,就可以将需要搜索的代码行数减少一半。经过 6 步之后 (这比 100
小多了),你将会找到那或者两行出错的代码,至少理论上是这样。
在实践中,可能并不能很好的确定程序的 ‘‘中间部分” 是什么,也有可能并不是那么好
检查。计算行数并且取其中间行是没有意义的。相反,多考虑下程序中哪些地方比较容
易出问题,或者哪些地方比较容易进行检查。然后选定一个检查点,在这个断点前后出
现 bug 的概念差不多。

8、提高调试效率

当你操作较大的数据集时,通过打印并手工检查数据来调试很不方便。下面是针对调试
大数据集的一些建议:
缩小输入 (Scale down the input): 如果可能,减小数据集合的大小。例如,如果程序
读入一个文本文件,从前 10 行开始分析,或是找到更小的样例。你可以选择编辑
读入的文件,或是 (最好) 修改程序使它只读入前 n 行。
如果出错了,你可以将 n 缩小为会导致该错误的最小值,然后在查找和解决错误
的同时,逐步增加 n 的值。
检查摘要和类型 (Check summaries and types): Instea 考虑打印数据的摘要,而不是
打印并检查全部数据集合:例如,字典中项的数目或者数字列表的总和。
运行时错误的一个常见原因,是值的类型不正确。为了调试此类错误,打印值的
类型通常就足够了。
编写自检代码 (Write self-checks): 有时你可以写代码来自动检查错误。例如,如果
你正在计算数字列表的平均数,你可以检查其结果是不是大于列表中最大的元素,
或者小于最小的元素。这被称作 ‘‘合理性检查’’,因为它能检测出 ‘‘不合理的’’ 结
果。
另一类检查是比较两个不同计算的结果,来看一下它们是否一致。这被称作 ‘‘一
致性检查’’。

格式化输出 (Format the output): 格式化调试输出能够更容易定位一个错误。
重申一次,你花在搭建脚手架上的时间能减少你花在调试上的时间。

9、如何找到bug

在调试一个程序的时候,特别是调试一个很难的错误时,应该做到以下五点:
细读: 检查你的代码,仔细地阅读,并且检查是否实现了你的期望。
运行: 通过修改和运行不同的版本来不断试验。通常,如果你在程序中正确的地方打
印了正确的东西,问题会变得很明显,但是有时你不得不搭建一些脚手架。
思考: 花些时间思考!错误的类型是什么:语法、运行时、语义?你从错误信息或者
程序的输出中能获得什么信息?什么类型的错误能引起你看到的问题?问题出现
前,你最后的修改是什么?
小黄鸭调试法 (rubberducking): 如果将你的问题解释给别人听,有时你会发现在解
释完问题之前就能找到答案。你通常并不需要真的去问另外一个人;你可以对着
一个小黄鸭说。这就是著名的小黄鸭调试法 (rubber duck debugging) 的由来。这
可不是我编造的,看看维基的解释。
回退: 有时候,最好的做法是回退,撤销最近的修改,直到你回到一个能运行并且你
能理解的程序。然后你可以开始重建。


初级程序员有时陷入这些步骤之一,忘记了还可以做其他的事情。事实上,每种方法都
有失败的可能。
例如,如果程序是一个排版错误,读代码可能有帮助,但是如果问题是概念理解错误,
则未必是这样。如果你不理解程序要做什么,可能读 100 遍程序都不会发现错误,因为
错误在你的头脑中。
试验可能会有帮助,特别是如果你运行简单短小的测试。但是,如果你不思考或者阅读
你的代码,就直接进行实验,你可能陷入一种我称为“随机游走编程”的模式。这指的是
随机修改,直到程序通过测试。不用说,随机游走编程会花费很长的时间。
你必须花时间思考。调试就像是一门实验科学。你应该至少有一个关于问题是什么的假
设。如果有两个或者更多的可能,试着考虑利用测试消除其中一个可能。
但是,如果有太多的错误,或者你正试图修复的代码太大、太复杂,即使最好的调试技
巧也会失败。有时,最好的选择是回退,简化程序,直到你获得一个正常运行并且能理
解的程序。
初级程序员经常不愿意回退,因为他们舍不得删除一行代码 (即使它是错误的)。如果能
让你好受些,在你开始精简之前,可以将你的代码拷贝到另一个文件中。然后你再把修
改后的代码一块一块地拷贝回去。
发现一个错误,需要阅读、运行、沉思、和时而的回退。如果其中某个步骤没有进展,
试一下其它的。

FAQ

A.1 语法错误

通常一旦找出是哪种语法错误,就容易修正。不幸的是,抛出的错误消息通常没什么帮
助。最常见的错误消息是 SyntaxError: invalid syntax 和 SyntaxError: invalid token ,都没
有提供很多信息。
另一方面,这些错误消息会告诉你程序的哪里出现了错误。实际上,它告诉你 Python
是在哪里发现的问题,但这并一定就是出错的地方。有时,错误出现在错误消息出现的
位置之前,通常就在前一行。
如果你是一点一点地增量式地写的代码,你应该能够知道错误在哪里。一般就在你最后
添加的那行代码里。
如果你是从书上复制的代码,那请仔细地从头和书中的代码对照。一个一个字母地比
照。同时,记住也可能是书上就错了,所以如果你发现看上去像语法错误的地方,那可
能就是了。下面是避免大部分常见语法错误的一些方法:
        1. 确保你没有使用 Python 的关键字作为变量名称。
        2. 检查你在每个复合语句首行的末尾都加了冒号,包括 for,while,if,和 def 语句。
        3. 确保代码中的字符串都有匹配地引号。确保所有的引号都是 ‘‘直引号1’’,而不是
        ‘‘花引号2’’。
        4. 如果你有带三重引号的多行字符串,确保你正确地结束了字符串。一个没有结束
        的字符串会在程序的末尾产生 invalid token 错误,或者它会把剩下的程序看作字
        符串的一部分,直到遇到下一个字符串。第二种情况下,可能根本不会产生错误!
        5. 一个没有关闭的操作符((,{ 以及 [ )使得 Python 把下一行继续看作当前语句
        的一部分。通常下一行会马上提示错误消息。
        6. 检查条件语句里面的 == 是不是写成了 = 。
        7. 确保每行的缩进是符合要求。Python 能够处理空格和制表符,但是如果混用则会
        出错。避免该问题的最好方法是使用一个了解 Python 语法、能够产生一致缩进
        的纯文本编辑器。
        8. 如果代码中包含有非 ASCII 字符串(包括字符串和注释),可能会出错,尽管
        Python 3 一般能处理非 ASCII 字符串。从网页或其他源粘贴文本时,要特别注意。
        如果上面的方法都不想,请接着看下一节...

A.1.1 我不断地改代码,但似乎一点用都没有。

如果解释器说有一个错误但是你怎么也看不出来,可能是因为你和解释器看的不是同
一个代码。检查你的编码环境,确保你正在编辑的就是 Python 试图要运行的程序。
如果你不确定,试着在程序开始时制造一些明显、故意的语法错误。再运行一次。如果
解释器没有提示新错误,说明你没有运行新修改的代码。
有可能是以下原因:
• 你编辑了文件,但是忘记了在运行之前保存。有一些编程环境会在运行前自动保
存,有些则不会。
• 你更改了文件的名称,但是你仍然在运行旧名称的文件。

• 开发环境的配置不正确。
• 如果你在编写一个模块,使用了 import 语句,确保你没有使用标准 Python 模块的
名称作为模块名。
• 如果你使用 import 来载入一个模块,记住你必须重启解释器或者使用 reload 才能
重新载入一个修改了的文件。如果你导入一个模块两次,第二次是无效的。
如果你依然解决不了问题,不知道究竟是怎么回事,有一种办法是从一个类似“Hello,
World!”这样的程序重头开始,确保你能运行一个已知的程序。然后逐渐地把原来程序
的代码粘贴到新的程序中。

A.2 运行时错误

一旦你的程序语法正确,Python 就能够编译它,至少可以正常运行它。接下来,可能
会出现哪些错误?

A.2.1 我的程序什么也没有做。

在文件由函数和类组成,但并没有实际调用函数执行时,这个问题是最常见的。你也可
能是故意这么做的,因为你只打算导入该模块,用于提供类和函数。
如果你不是故意的,确保你调用了一个函数来开始执行,请确保执行流能够走到函数调
用处(参见下面“执行流”一节)。

A.2.2 我的程序挂死了。

如果一个程序停止了,看起来什么都没有做,这就是“挂死”了。通常这意味着它陷入了
无限循环或者是无限递归。
• 如果你怀疑问题出在某个循环,在该循环之前添加一个打印语句,输出“进入循环”,
在循环之后添加一个打印“退出循环”的语句。
运行程序。如果打印了第一条,但没有打印第二条,那就是进入了无线循环。跳
到下面“无限循环”一节。
• 大多数情况下,无限递归会造成程序运行一会儿之后输出

RuntimeError:Maximum recursion depth exceeded

错误。如果发生了这个错误,跳到下面 ‘‘无限递归’’ 一节。
如果没有出现这个错误,但你怀疑某个递归方法或函数有问题,你仍可以使用“无
线递归”一节中的技巧。

• 如果上面两种方法都没用,开始测试其他的循环和递归函数或方法是否存在问题。
• 如果这也没有用,那有可能你没有弄懂程序的执行流。跳到下面 ‘‘执行流’’ 一节。

A.2.3  执行流

如果你不确定程序执行的过程,在每个函数的开始处添加打印语句,打印类似 ‘‘进入函
数 foo’’ 这样的信息,foo 是你的函数名。
现在运行程序时,就会打印出每个函数调用的轨迹。
运行程序时产生了异常。

如果在运行时出现了问题,Python 会打印出一些信息,包括异常的名称、产生异常的
行号和一个回溯 (traceback)。
回溯会指出正在运行的函数、调用它的上层函数以及上上层函数等等。换言之,它追踪
进行到目前函数调用所调用过的函数,包括每次函数的调用所在的行号。
第一步是检查程序中发生错误的位置,看你能不能找出问题所在。下面是一些常见的运
行时错误:
命名错误 (NameError): 你正在使用当前环境中不存在的变量名。检查下名称是否拼写
正确,或者名称前后是否一致。还要记住局部变量是局部的。你不能在定义它们
的函数的外面引用它们。
类型错误 (TypeError): 有几种可能的原因:
• 值的使用方法不对。例如:使用除整数以外的东西作为字符串、列表或元组
的索引下标。
• 格式化字符串中的项与传入用于转换的项之间不匹配。如果项的数量不同或
是调用了无效的转换,都会出现这个问题。
• 传递给函数的参数数量不对。如果是方法,查看方法定义是不是以 self 作为
第一个参数。然后检查方法调用;确保你在一个正确的类型的对象上调用方
法,并且正确地提供了其它参数。
键错误 (KeyError): 你尝试用字典没有的键来访问字典的元素。如果键是字符串,记住
它是区分大小写的。
属性错误 (AttributeError): 你尝试访问一个不存在的属性或方法。检查一下拼写!你
可以使用内建函数 dir 来列出存在的属性。
如果一个属性错误表明一个对象是 NoneType ,那意味着它就是 None 。因此问题不
在于属性名,而在于对象本身。
对象是 None 的一个可能原因,是你忘记从函数返回一个值;如果程序执行到函数
的末尾没有碰到 return 语句,它就会返回 None 。另一个常见的原因是使用了列表
方法的结果,如 sort ,这种方法返回的是 None 。
索引错误 (IndexError): 用来访问列表、字符串或元组的索引要大于访问对象长度减一。
在错误之处的前面加上一个打印语句,打印出索引的值和数组的长度。数组的大
小是否正确?索引值是否正确?
Python 调试器 (pdb) 有助于追踪异常,因为它可以让你检查程序出现错误之前的状态。
你可以阅读了解更多关于 pdb 的细节。

A.2.4 我加入了太多的打印语句以至于输出刷屏。

使用打印语句来调试的一个问题,是你可能会被泛滥的输出所埋没。有两种途径来处
理:简化输出或者是简化程序。
为了简化输出,你可以移除或注释掉不再需要的打印语句,或者合并它们,或者格式化
输出便于理解。
为了简化程序,有几件事情可以做的。首先,缩减当前求解问题的规模。例如,如果你
在检索一个列表,使用一个小列表来检索。如果程序从用户获得输入,给一个会造成问
题的最简单的输入。
其次,清理程序。移除死代码,并且重新组织程序使其易于理解。例如,如果你怀疑问
题来自程序深度嵌套的部分,尝试使用简单的结构重写它。如果你怀疑是一个大函数的
问题,尝试分解它为小函数并分别测试。
通常,寻找最小化测试用例的过程能够引出 bug。如果你发现一个程序在一种条件下运
行正确,在另外的条件下运行不正确,这能够给你提供一些解决问题的线索。
类似的,重写代码能够让你发现难找的 bug。如果你做了一处改变,认为不会影响程序
但是却事实证明相反,这也可以给你线索。

A.3 语义错误

在某些程度上,语义错误是最难调试的,因为解释器不能提供错误的信息。只有你知道
程序本来应该是怎么样做的。
第一步是在程序代码和你看到的表现之间建立连接。你需要首先假设程序实际上干了
什么事情。这种调试的难处之一,是电脑运行的太快了。
你会经常希望程序能够慢下来好让你能跟上它的速度,通过一些调试器 (debugger) 就
能做到这点。但是有时候,插入一些安排好位置的打印语句所需的时间,要比你设置好
调试器、插入和移除断点,然后“步进”程序到发生错误的地方要短。

A.3.1 我的程序不能工作。

你应该问自己下面这些问题:
• 是不是有你希望程序完成的但是并没有出现的东西?找到执行这个功能的代码,
确保它是按照你认为的方式工作的。

• 是不是有些本不该执行的代码却运行了?找到程序中执行这个功能的代码,然后
看看它是不是本不应该执行却执行了。
• 是不是有一些代码的效果和你预期的不一样?确保你理解了那部分的代码,特别
是当它涉及调用其它模块的函数或者方法。阅读你调用的函数的文档。尝试写一
些简单的测试用例,来测试他们是不是得到了正确的结果。
在编程之前,你需要先建立程序是怎样工作的思维模型。如果你写出来的代码并非按照
你预期的工作,问题经常不是在程序本身,而是你的思维模型。
纠正思维模型最好的方,是把程序切分成组件(就是通常的函数和方法),然后单独测
试每个组件。一旦你找到了模型和现实的不符之处,你就能解决问题了。
当然,你应该在写代码的过程中就编写和测试组件。如果你遇到了一个问题,那只能是
刚写的一小段代码才有可能出问题。

A.3.2 我写了一个超大的密密麻麻的表达式,结果它运行得不正确。

写复杂的表达式是没有问题的,前提是可读,但是它们很难调试。通常把复杂的表达式
打散成一系列临时变量的赋值语句,是一个好做法。因为变量名提供了额外的信息,也更容易调试,因为你可以检查中间变量的类型和值。调试表达式的一个好办法,是添加括号来显式地指定计算顺序 只要你不太确定计算的顺序,就用括号。这样不仅能确保程序正确(按照你认为的方式
工作),而且对于那些记不住优先级的人来说更加易读。

A.3.3 我真的是没办法了,我需要帮助。

首先,离开电脑几分钟吧。电脑发出的辐射会影响大脑,容易造成以下症状:
• 焦躁易怒
• 迷信 (‘‘电脑就是和我作对”) 和幻想 ( ‘‘只有我反着带帽子程序才会正常工作”)。
• 随机漫步编程(试图编写所有可能的程序,选择做了正确的事情的那个程序)。
如果你发现你自己出现上述的症状,起身走动走动。当你冷静之后,再想想程序。它在
做什么?它异常表现的一些可能的原因是什么?上次代码正确运行时什么时候,你接
下来做了什么?
有时,找到一个 bug 就是需要花很长的时间。我经常都是在远离电脑、让我的思绪飞
扬时才找到 bug 的。一些寻找 bug 的绝佳地点是火车上、洗澡时、入睡之前在床上。

A.3.4我不干了,我真的需要帮助。

这个经常发生。就算是最好的程序员也偶尔被难住。有时你在一个程序上工作的时间太
长了,以至于你看不到错误。那你该是休息一下双眼了。
当你拉某人来帮忙之前,确保你已经准备好了。你的程序应该尽量简单,你应该应对造
成错误的最小输入。你应该在合适的地方添加打印语句(打印输出应该容易理解)。你
应该对程序足够理解,能够简洁地对其进行描述。
当你拉某人来帮忙时,确保提供他们需要的信息:
• 如果有错误信息,它是什么以及它指出程序的错误在哪里?
• 在这个错误发生之前你最后做的事情是什么?你写的最后一行代码是什么,或者
失败的新的测试样例是怎样的?
• 你至今都尝试了哪些方法,你了解到了什么?
你找到了 bug 之后,想想你要怎样才能更快的找到它。下次你看到相似的情况时,你
就可以更快的找到 bug 了。
记住,最终目标不是让程序工作,而是学习如何让程序正确工作。

注:本文选择性摘自《Think Python》 Allen Downey著,原书pdf版链接:

百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固,支持教育网加速,支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.baidu.com/s/1UBrdVVF28dW3FJMulFeF-w?pwd=wv4z提取码:wv4z

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

*OASIS*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值