实现一个功能,完成一段代码编写,能做到 bug free 的程度对于初学者来说是不太可能的事情。除非编码的内容极为简单,随着逻辑和增加和复杂度的增长,出错,未考虑到的逻辑的概率大大增加。所以调试代码是每个编程者都需要面对的问题。
为了避免“编程 5 分钟,调试 2 小时”,我们需要知道一些基本的调试代码的方法。
选择一个好的 IDE
IDE 能够最大程度的避免低级错误。
作为初学者,占据问题一般以上往往是低级错误。比如
-
缩进错误(空格、tab混用)
-
变量前后命名不一致(拼写错误)
-
函数调用时传递的参数不全或不对
-
少引号括号、用了中文全角
等等。这些人眼很难一下发现的错误,代码编辑器可以在写代码的时候就给你提示出来。
以及提供的
- 智能提示
- 语法高亮
- 自动补全
等特性,能够极大程度帮助提高编写效率和减少出现低级错误。
重点是 error 信息
大多数程序报错的同时都会抛出异常信息。这些信息是定位 bug 追根溯源的起点,比如下面的一个例子:
编程坏习惯:在 debug 过程不看错误信息
一定要看报错信息。害怕看报错信息是初学者中最大的困难,也许错误的原因千奇百怪,往往错误类型无非就那些,多看几次大概就能够熟悉了。
有些报错的位置也许看不出问题,这时要考虑执行的状态,结合上下文来判断执行过程。试着往代码段前面看看。
Stack Overflow 可以搜索你的错误信息,里面往往会有一些解答。
调试方式和工具
print
print 是最万能的。用来打印当前变量的值或状态。
-
**确定程序的运行路径。**一个函数有没有被调用,一个 if 块有没有被执行,一个 while 循环执行了几次,到了哪一步中断了,都可以通过 print 出相关信息来查看。
-
**查看变量的状态。**程序自身的报错会告诉你发生了什么错误,但你还需要找出为什么会发生错误。通过 print 输出出错语句涉及到的相关变量的值和类型,可以帮助分析出错原因。
-
**找出出错位置。**往往错误的原因并不在报错的位置,所以多输出一些标记,多 print 不同位置的变量值,查看变量在运行过程中值的变化情况,可以观察是在哪里发生了问题。
用print
最大的坏处是将来还得删掉它。想想程序里到处都是print
,运行结果也会包含很多冗余信息。
assert
断言
assert
适用于我们已经知道代码执行的预期结果,assert 后面跟一个表达式。
assert
的实质是:
|
|
在使用 assert
的问题在于,一旦代码执行的结果不符合预期,就会引发错误。对异常处理缺乏灵活性。
logging
和assert
比,logging
不会抛出错误,而且可以输出到文件。相比于print
语句有了更好的控制,供了日志信息的分级,格式化,过滤等功能。
logging 模块为错误的严重程度提供了不同层级,从低到高依次为:
等级 | 什么时候使用 |
---|---|
DEBUG |
详细信息,通常仅在Debug时使用。 |
INFO |
程序正常运行时输出的信息。 |
WARNING |
表示有些预期之外的情况发生,或者在将来可能发生什么情况。程序依然能按照预期运行。 |
ERROR |
因为一些严重的问题,程序的某些功能无法使用了。 |
CRITICAL |
发生了严重的错误,程序已经无法运行。 |
pdb(了解)
pdb 是 Python Debugger, 让程序以单步方式运行,可以随时查看运行状态。
IDE 断点调试(安利)
现代 IDE 提供了非常强大的调试工具,其中之一就是我们会经常用到的断点调试。
断点调试的步骤分为 3 步:
-
标记断点
所谓断点(Breakpoint),即为你想要程序运行到何处暂停的位置。
-
进入断点位置
程序运行到断点的位置,这个时候程序会暂停,可以查看当前环境下各种数据的信息。
-
从断点处开始执行,直到找到与预期行为不符合的位置
从断点处可以单步执行,每执行一步可以观察变化是否符合预期,直到找到程序执行不符合预期的行为。
断点调试可以让我们深入到程序里,观察每一行代码的执行。
PyCharm 断点调试
以 PyCharm 为例,示例代码如下:
|
|
假设我们想要了解程序执行的过程,我们把断点设置在 while
语句前:
接下来进入调试模式:
可以很清楚地看到以下信息:
- 线程信息
- 执行到断点处所有变量信息
此外,最重要的是此时能够执行的操作,从左到右依次为:
-
Step Over
跳过当前代码行并带您到下一行,即使突出显示的行中有方法调用。方法的实现被跳过,您直接移动到调用者方法的下一行。
-
Step Into
步入该方法以显示其内部发生的情况。当您不确定该方法是否返回正确结果时,请使用此选项
-
Step Into My Code
只步入自己的代码并防止调试器进入库类
-
Force Step Into
状态为灰色的按钮
-
Step Out
跳出当前方法并带您进入调用方方法
-
Run to Cursor
继续执行,直到到达插入符号的位置。所需要查看位置里断点位置太远时,此操作非常有用
接下来我们只使用 Step Into 的方式,一步步执行并查看变量变化过程。
VsCode 断点调试
VsCode 也提供了调试功能。类似地,在代码行数字旁边标记好断点,
点击 运行-> 启动调试 或 按下 F5,进入调试模式
变量区和操作去与上面 PyCharm 基本一致
调试思路与其他
- 问题定位
- 与预期行为的比较
- 猜想出错原因
- 测试验证
首先的是进行问题定位。
如果程序执行出错退出,这种错误是“显式错误”。通过 IDE 对错误信息的描述,可以知道问题出错的位置。
但造成错误的 root cause 很可能潜藏在更深处的地方,而这里报错的定位是最重要的线索,我们会从这里切入去找到真相。
没有异常信息的“错误”,可以称之为“隐式错误”,在程序运行的层面上看来,它正在执行一条条正确的语句,然而结果并不符合我们的预期。
第二步是对预期行为的比较。调试的行为是观察程序执行的步骤,当程序运行的结果与预期的行为不一致时,它一定有一个合理的解释。至于为什么会这样的原因可能静静地躺在某个地方,等待着你找到它。
作出猜测,然后验证。这个行为将占据调试行为的大部分时间,许多错误的根源截然不同,但是表层显示出的行为缺即为相似。
Debug 就像是扮演侦探,需要收集各种细节,对一些常见的“犯罪手法”有这足够的了解,越熟悉“案件”(程序),“作案工具”(编程语言)的话,也就越容易找到“罪犯”(Bug)。