如何在 IDE 中调试代码

实现一个功能,完成一段代码编写,能做到 bug free 的程度对于初学者来说是不太可能的事情。除非编码的内容极为简单,随着逻辑和增加和复杂度的增长,出错,未考虑到的逻辑的概率大大增加。所以调试代码是每个编程者都需要面对的问题。

为了避免“编程 5 分钟,调试 2 小时”,我们需要知道一些基本的调试代码的方法。

选择一个好的 IDE

IDE 能够最大程度的避免低级错误。

作为初学者,占据问题一般以上往往是低级错误。比如

  • 缩进错误(空格、tab混用)

  • 变量前后命名不一致(拼写错误)

  • 函数调用时传递的参数不全或不对

  • 少引号括号、用了中文全角

等等。这些人眼很难一下发现的错误,代码编辑器可以在写代码的时候就给你提示出来。

以及提供的

  • 智能提示
  • 语法高亮
  • 自动补全

等特性,能够极大程度帮助提高编写效率和减少出现低级错误。

debug-syntax-error重点是 error 信息

大多数程序报错的同时都会抛出异常信息。这些信息是定位 bug 追根溯源的起点,比如下面的一个例子:

debug-error-msg编程坏习惯:在 debug 过程不看错误信息

一定要看报错信息。害怕看报错信息是初学者中最大的困难,也许错误的原因千奇百怪,往往错误类型无非就那些,多看几次大概就能够熟悉了。

有些报错的位置也许看不出问题,这时要考虑执行的状态,结合上下文来判断执行过程。试着往代码段前面看看。

Stack Overflow 可以搜索你的错误信息,里面往往会有一些解答。

调试方式和工具

print

print 是最万能的。用来打印当前变量的值或状态。

  • **确定程序的运行路径。**一个函数有没有被调用,一个 if 块有没有被执行,一个 while 循环执行了几次,到了哪一步中断了,都可以通过 print 出相关信息来查看。

  • **查看变量的状态。**程序自身的报错会告诉你发生了什么错误,但你还需要找出为什么会发生错误。通过 print 输出出错语句涉及到的相关变量的值和类型,可以帮助分析出错原因。

  • **找出出错位置。**往往错误的原因并不在报错的位置,所以多输出一些标记,多 print 不同位置的变量值,查看变量在运行过程中值的变化情况,可以观察是在哪里发生了问题。

print最大的坏处是将来还得删掉它。想想程序里到处都是print,运行结果也会包含很多冗余信息。

assert 断言

assert 适用于我们已经知道代码执行的预期结果,assert 后面跟一个表达式。

assert 的实质是:

1
2
if not expression:
    raise AssertionError

在使用 assert 的问题在于,一旦代码执行的结果不符合预期,就会引发错误。对异常处理缺乏灵活性。

logging

assert比,logging不会抛出错误,而且可以输出到文件。相比于print语句有了更好的控制,供了日志信息的分级,格式化,过滤等功能。

logging 模块为错误的严重程度提供了不同层级,从低到高依次为:

等级 什么时候使用
DEBUG 详细信息,通常仅在Debug时使用。
INFO 程序正常运行时输出的信息。
WARNING 表示有些预期之外的情况发生,或者在将来可能发生什么情况。程序依然能按照预期运行。
ERROR 因为一些严重的问题,程序的某些功能无法使用了。
CRITICAL 发生了严重的错误,程序已经无法运行。

pdb(了解)

pdb 是 Python Debugger, 让程序以单步方式运行,可以随时查看运行状态。

IDE 断点调试(安利)

现代 IDE 提供了非常强大的调试工具,其中之一就是我们会经常用到的断点调试。

断点调试的步骤分为 3 步:

  1. 标记断点

    所谓断点(Breakpoint),即为你想要程序运行到何处暂停的位置。

  2. 进入断点位置

    程序运行到断点的位置,这个时候程序会暂停,可以查看当前环境下各种数据的信息。

  3. 从断点处开始执行,直到找到与预期行为不符合的位置

    从断点处可以单步执行,每执行一步可以观察变化是否符合预期,直到找到程序执行不符合预期的行为。

断点调试可以让我们深入到程序里,观察每一行代码的执行。

PyCharm 断点调试

PyCharm Debug Docs

以 PyCharm 为例,示例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def reverse(x: 'int') -> 'int':
    negative = (x < 0)
    x = abs(x)

    if 10 > x > -10:
        return x

    res = 0
    while x != 0:
        digit = x % 10
        x //= 10
        res = (10 * res) + digit

    if res > 2**31 - 1:
        return 0

    return res if not negative else -res


if __name__ == '__main__':
    nums = [1283, -9527]
    for num in nums:
        print(reverse(num))

假设我们想要了解程序执行的过程,我们把断点设置在 while 语句前:

pycharm-debug-entry

接下来进入调试模式:

pycharm-debug-mode

可以很清楚地看到以下信息:

  1. 线程信息
  2. 执行到断点处所有变量信息

此外,最重要的是此时能够执行的操作,从左到右依次为:

  • Step Over

    跳过当前代码行并带您到下一行,即使突出显示的行中有方法调用。方法的实现被跳过,您直接移动到调用者方法的下一行。

  • Step Into

    步入该方法以显示其内部发生的情况。当您不确定该方法是否返回正确结果时,请使用此选项

  • Step Into My Code

    只步入自己的代码并防止调试器进入库类

  • Force Step Into

    状态为灰色的按钮

  • Step Out

    跳出当前方法并带您进入调用方方法

  • Run to Cursor

    继续执行,直到到达插入符号的位置。所需要查看位置里断点位置太远时,此操作非常有用

接下来我们只使用 Step Into 的方式,一步步执行并查看变量变化过程。

VsCode 断点调试

VSCode Debug Docs

VsCode 也提供了调试功能。类似地,在代码行数字旁边标记好断点,

点击 运行-> 启动调试按下 F5,进入调试模式

变量区和操作去与上面 PyCharm 基本一致

vscode-debug-mode

调试思路与其他

  1. 问题定位
  2. 与预期行为的比较
  3. 猜想出错原因
  4. 测试验证

首先的是进行问题定位。

如果程序执行出错退出,这种错误是“显式错误”。通过 IDE 对错误信息的描述,可以知道问题出错的位置。

但造成错误的 root cause 很可能潜藏在更深处的地方,而这里报错的定位是最重要的线索,我们会从这里切入去找到真相。

没有异常信息的“错误”,可以称之为“隐式错误”,在程序运行的层面上看来,它正在执行一条条正确的语句,然而结果并不符合我们的预期。

第二步是对预期行为的比较。调试的行为是观察程序执行的步骤,当程序运行的结果与预期的行为不一致时,它一定有一个合理的解释。至于为什么会这样的原因可能静静地躺在某个地方,等待着你找到它。

作出猜测,然后验证。这个行为将占据调试行为的大部分时间,许多错误的根源截然不同,但是表层显示出的行为缺即为相似。

Debug 就像是扮演侦探,需要收集各种细节,对一些常见的“犯罪手法”有这足够的了解,越熟悉“案件”(程序),“作案工具”(编程语言)的话,也就越容易找到“罪犯”(Bug)。

updatedupdated2021-06-032021-06-03