028-86261949

当前位置:首页 > 技术交流 > Python的科学调试

Python的科学调试

2018/10/19 16:21 分类: 技术交流 浏览:27

如果程序可以运行,但程序行为和期待的或需要的不一致,就说明程序中存在个bug必须清除的逻辑错误。清除这类错误的最好方法是首先使用TDD(测试驱动的开发)来防止发生这一类错误,然而,总会有些bug没有避免,因此,即便使用TDD,调试也仍然是必须学习和掌握的技能。在这一小节中,我们将简要介绍一种调试方法,该方法基于科学的方法。这里对该方法进行了足够详细的解释,以至于看起来对“简单的”bug来说,这种方法未免过于繁琐。然而,通过有意识地遵循这一过程,我们可以避免“随机”调试浪费的时间,并且,过一段时间之后,我们会将该过程内置于思维和开发过程中,并可以下意识地遵循该流程,从而非常快。

为清除一个Bug, 我们必须采取如下一些步骤.

再现Bug

定位Bug

修复Bug

对修复进行测试

有时候,重现bug是容易的—每次运行时bg总是会出现;有时候则是困难的bug间歇性地发作。不管哪种情况,我们都应该尽量减少该bg的依赖性,也就是说,找到最少的输入与最小的处理量,并使其仍能产生该bg旦可以重现bug,我们就拥有了数据(输入数据与选项)和错误结果(这是必要的

借助于这些信息,我们才能应用科学方法来发现并修复bug)。该方法有以下3个步骤。

(1)设想一个解释(某种假设)可以合乎情理地导致该bug

(2)创建实验过程来测试该假设。

(3)运行实验。

运行实验应该有助于定位bug,也应该有助于找到解决方案。(稍后将介绍如何创建并运行实验。)一旦确定了怎样清除bug一并且通过版本控制系统对代码进行了检查,以便在必要的时候进行修复——就可以编写修复代码了。

修复代码准备好后必须对其进行测试。自然,测试的目的是看其试图修复的bug是否可以有效清除。仅有这个是不够的,虽然修复可能解决我们所关注的bug,但是修复代码本身也可能引入其他bug,并影响程序的其他方面。因此,除了对bug修复本身进行测试外,还必须运行程序的小测试用例,以便确信bug修复并没有引入其他任何副作用。

有些bug有特定的结构,因此,修复了某个bug的同时,总是应该再想一下,是否程序或其模块中的其他位置处也存在类似的bug。如果有,就检查一下,看看自己是否已经有了可以揭示该bug的测试用例。如果没有这样的测试用例,就添加:如果已经揭示了某些bug,就应该像前面描述的那样对其进行修复在对调试过程有了较好的整体了解之后,下面将集中讲述如何创建并运行实验以便对假设进行测试。我们首先从试图隔离bug开始。依赖于程序和bug本身的特性,我们可以编写测试用例对程序进行实验。比如,先提供可以被程序正确处理的数据,之后逐渐地对提供的数据进行改变,以便准确地发现在哪个位置处理失败。在知道了问题所在之后(不管是通过测试还是推理)。我们就可以对假设进行测试了。

应该进行怎样的假设呢?当然,最初的假设可以很简单。比如,怀疑在使用特定的输入数据或选项时,某个特定的函数或方法会返回错误数据。之后,如果这一假设是正确的,就可以对其进行细化,使其更加具体——比如,识别出函数(我们认为该函数在特定的情况下会进行错误运算)中某个特定的语句或套件。要对假设进行测试,需要检查函数接受的参数及其本地变量的值,还有函数的返回值(就在其返回之前)。之后,可以使用已知会导致错误的数据运行程序,并检查可疑函数。如果进入函数的参数不是我们所期待的,那么问题可能出现在调用栈中更远的位置,因此应该再一次开始这一过程,不过这一次针对的函数是调用前面可疑函数的函数。但是,如果所有的输入参数总是有效的,就必须查看局部变量和返回值。如果这些也总是对的,就需要提出新的假设,因为可疑函数的行为是正确的。如果返回值是错的,就必须对该函数进行进一步的研究。

在实践中,怎样进行实验,也就是说,怎样对假设(假定某个特定函数有错误行进行测试?有一种方法是在思维中“执行”一下函数一对很多小函数和实践中的大函数这是可能的,并且有一个额外的好处是有助于熟悉函数的行为。充其量,这可以导向一个改进的或更特定的假设一比如,某个特定的语句或套件是问题所在。为了正确进行实验,我们必须对程序进行监测,以便了解函数被调用时会发生什么。有两种方法可以对程序进行监控:一种是侵入性的方法,即插入 print()语句;另一种(通常的)是非侵入性的方法,即使用调试器。这两种方法的目的一致,也同样有效,不过有些程序员有很强的使用偏好。我们将简要地描述这两种方法,从 print()的使用开始.

使用print()语句时,可以在函数的开始部分放置一条print()语句,并且打印函数的参数.之后,就在每条return语句之前(如果没有return语句,就在函数的末尾)添加一条print(locals(),”\n”)语句,其中,内置的locals()函数会返回一个字典,该字典的键是本地变量的名称,值测试本地变量的值,当然,我们也可以值是简单第打印出我们特殊关注的变量. 注意到上面的语句中我们添加了额外的一个换行----在第一个print()语句中我们应该也这样做, 这样的好处是在每组变量之间有一个空白的行,有助于清晰描述(直接插入print()语句的一条替代方法是使用某种类型的logging decortor).

如果在运行程序时发现参数是对的,但返回值是错的,就可以确定已经定位了bug的源头,并可以对该函数进行进一步的研究如果仔细查看该函数仍然无法发现问题所在,我们可以简单地在函数中间插入一条新的 print(locals(),"\n")语句。再次运行程序之后,就应该知道问题是出在函数的前一半还是后一半,并在出问题的那一半的中间再次插入一条pint(locals(),"\n")语句,重复这一过程,直到发现导致错误的具体语句。这可以非常快捷地让我们找到问题所在一大多数情况下,定位问题就已经解决了问题的一半添加print()语句的一种替代方法是使用调试器。 Python有两个标准的调试器。一个是作为模块(pdb)提供的,该模块可以在控制台中交互式地使用一比如 python3 –m pdb_my_program.py。(当然,在 Windows平台上, python3也可能是类似于C: \Python3\python.exe)然而,使用该模块最容易的方法是在程序本身添加 import pdb语句,并将pdb.set_trace()语句作为第一条语句添加到我们要检查的函数中。程序运行时,pdb会在pdb.set_trace()调用后立即终止,并允许我们逐步调试该程序、设置断点、检查变量等。

 

 

#标签:Python,科学调试