论程序的调试技巧doc.docx

上传人:b****3 文档编号:10573646 上传时间:2023-05-26 格式:DOCX 页数:10 大小:23.11KB
下载 相关 举报
论程序的调试技巧doc.docx_第1页
第1页 / 共10页
论程序的调试技巧doc.docx_第2页
第2页 / 共10页
论程序的调试技巧doc.docx_第3页
第3页 / 共10页
论程序的调试技巧doc.docx_第4页
第4页 / 共10页
论程序的调试技巧doc.docx_第5页
第5页 / 共10页
论程序的调试技巧doc.docx_第6页
第6页 / 共10页
论程序的调试技巧doc.docx_第7页
第7页 / 共10页
论程序的调试技巧doc.docx_第8页
第8页 / 共10页
论程序的调试技巧doc.docx_第9页
第9页 / 共10页
论程序的调试技巧doc.docx_第10页
第10页 / 共10页
亲,该文档总共10页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

论程序的调试技巧doc.docx

《论程序的调试技巧doc.docx》由会员分享,可在线阅读,更多相关《论程序的调试技巧doc.docx(10页珍藏版)》请在冰点文库上搜索。

论程序的调试技巧doc.docx

论程序的调试技巧doc

论程序的调试技巧

论程序的调试技巧2010-10-2420:

29【关键字】调试技巧、测试方法、测试用例设计

【摘要】本文结合作者自身经验,对竞赛中程序的调试技巧做了详细的阐述和总结。

在介绍了编程中常见的错误类型和集成环境的调试工具之后,给出了一般调试流程,并着重讲述了其中的动态查错技巧,做了一定的归纳。

最后通过一个调试实例来体现本文所论述的调试技巧的具体应用。

【正文】

一、程序调试的必要性

程序设计过程中,错误是在所难免的。

虽然有些程序员认为一个程序可以做到完美无瑕,但实际情况却并非如此,不然就不会有人对Windows怨气冲天了。

尽管信息学竞赛中所编的程序从来不会像Windows那样庞大,最多也是仅仅几百K而已,但由于时间有限,选手们的程序难免有疏漏之处。

因此,调试就成了极其重要的一环。

如何在紧迫的时间内快速准确地发现并改正错误,正是本文所要讨论的问题。

二、常见错误类型归纳

《孙子兵法》云:

"知己知彼,百战不殆。

"对于程序调试者来说,程序中的错误就好比是敌人,如能准确把握敌人的情况,无疑是极为有利的。

下面我们就来对常见的一些错误类型进行归纳并给出解决方法。

1、思路错误

这要看是基本算法错误还是功能缺陷。

前者需要重写大部分代码,是否重写则根据时间是否充裕而定,后者只需增加一部分代码,再修改某些地方,这时应全面考虑,以防遗漏应该修改的地方。

2、语法错误

这个没什么可说的,作为一名信息学竞赛的选手,应该对自己选择的编程语言的语法了如指掌,具体在这里就不多讲了。

3、书写错误

这种错误令人十分头痛,一般的书写错误在编译时都能找出来,但如果你在表达式中用到变量j时误写成了i,不但编译程序找不出来,自己找时也由于两者样子比较相似,难以发现。

排除这种错误只能靠"细心"两字,具体可使用下面要介绍的静态查错法。

4、输出格式错误

由于现在信息学竞赛采用黑箱测试法,由于输出格式错误而导致失分的例子屡见不鲜。

一个标点,一个空格,都会导致最后的悔恨。

因此,在调试时先要核对输出格式,针对不同输出格式多设计几个测试用例,以防一失足成千古恨。

5、其它编程时易犯的错误

除了上面所说的错误类型外,其它就属于编程时在细节上考虑不周所造成的了。

下面仅列举其中一些较为隐蔽的错误。

只有靠平时不断总结积累,才能真正的做到"知己知彼"。

①变量未赋初值

看下面的程序段

Fori:

=1toNDoIfA[i]MaxThenMax:

=A[i];

WriteLn(Max);

这个程序段的原意显然是要输出数组A中最大的数。

但由于它遗漏了将Max赋初值的语句,因此很可能会出现输出的数并不在数组A中的错误。

应该在过程开头添上一句Max:

=-MaxInt;。

养成变量使用前先赋初值的习惯能预防许多较隐蔽的错误。

②中间运算越界

看下面这两句语句

A:

=1000;

B:

=A*ADiv100;

其中A,B都是Integer类型。

按照我们的想法,1000*1000Div100=10000。

然而当我们察看B的值的时候,却发现B等于169。

原因是Pascal在进行编译时,总是先计算出A*A,把它放到一个中间变量中,然后再计算出最后结果放入B中。

而A*A超出了Integer的范围,这就是造成错误的根本原因。

要使Pascal能报告这类错误,只要打开编译开关Q即可。

对此类错误解决方法是使用强制类型转换,写成B:

=LongInt(A)*ADiv100。

编译程序会自动把中间变量规定为LongInt类型,就不会越界了。

③局部变量与全局变量同名造成概念混乱

这个实际上不能算错误,然而有许多错误正是因此而起。

一个常见的错误是当我们在过程中使用全局变量时,忘记了自己在该过程中还定义了一个同名的局部变量,从而使得实际的程序与我们的思路不一致;另一个常见的错误是局部变量忘记定义,在过程(函数)中实际上使用的是全局变量,而出现错误往往是在这个过程(函数)之外某个需要使用该全局变量的地方,这就增加了调试的难度。

因此,应该尽量避免使用同名变量。

④If-Then-Else语句混乱

Pascal对If-Then-Else语句的规定是:

If-Then语句可以没有Else语句与之相匹配;Else语句总是匹配最近的If-Then语句。

这一点使得我们在使用嵌套的If-Else语句时容易出错。

如下面这个例子:

If条件aThenIf条件bThen代码段bElse代码段a

我们的原意是让代码段a在条件a不成立时执行,但由于Else语句总是匹配最近的If-Then语句,因此这个Else是与If条件bThen这个语句相匹配的,也就是说代码段a要满足条件a成立且条件b不成立时才会执行,与我们原意相去甚远。

解决方法是在需要的地方加一个空的Else,就如上面的例子,要在If条件bThen语句后面加一个空的Else。

⑤实数比较出错

在比较两个实数是否相等时,如果直接用等号,往往会造成错误。

这是浮点运算存在误差所造成的,解决办法是使用两数差的绝对值与一个相对极小量进行比较,一般说来如果abs(a-b)1e-8,则可认为a=b。

三、集成环境的调试工具

对于一个战士来说,对自己手中的武器性能特点应该了如指掌。

对于程序调试者来说,调试工具就相当于武器,熟练掌握调试工具,充分发挥它的性能,对于迅速找出错误,加快我们的调试速度有着极大的帮助。

下面就对集成环境提供的调试工具做一些介绍。

调试时主要使用的两个菜单是Run和Debug。

Run菜单提供了各种程序执行方式,而Debug菜单提供了对变量的观察,修改以及断点等功能。

程序的执行方式有四种:

1、Run,运行整个程序(Ctrl+F9),该方式常用在总体测试上。

一般每一个测试用例都应先用该方式执行程序,如果输出答案正确就可以直接转到下一个测试用例,免去了不必要的时间。

即使发现错误也只不过比直接进入模块调试增加了一点点时间,是完全值得的。

2、Stepover,单步执行,把整个过程(函数)视为单步一次执行(F8),该方式常用在模块调试时期,可以通过观察变量在模块执行前后的变化情况来确定该模块中是否存在错误,也可以用来跳过已测试完毕的模块。

3、Traceinto,单步执行,对于过程(函数)进入到内部一步步执行(F7),该方式常用在底层调试时期,可以跟踪程序的每步执行过程。

它的优点是容易直接定位错误,缺点是调试速度较慢,尤其是当错误位于程序后部时。

所以一般是采用先用模块调试法尽量缩小错误范围,然后使用第4种执行方式和断点来快速跳过没有出现错误的部分,最后才是用该方式来逐步跟踪找出错误。

4、Gotocursor,执行到光标处(F4),之所以把这种方式放在最后介绍,是因为这种方式的灵活度较大,不但可以一次执行一行,也可以一次执行多行;可以直接跳过过程(函数),也可以进入过程(函数)内部。

它有断点的定位能力强的优点,又比断点更加灵活。

正确适当地使用这种方式可以大大加快我们调试的速度,这需要靠丰富的调试经验。

可以参考后面的调试实例。

Debug菜单中最常用选项是Watch和AddWatch,这两个用于跟踪观察变量和表达式在程序执行过程中值的变化,这样就可以随时检查它们是否按照算法要求输出,是否符合正确答案。

大多数错误在调试时都可以只使用它们以及上面的四种执行方式被检查出来。

有的时候,虽然知道该模块有错,但一时无法找到错误所在,并且上面所讲的后三种执行方式都难以快速定位。

例如对于一个程序,我们需要它执行到某个语句并满足某个条件时停下来,Gotocursor只能保证执行到这个语句时停下来,却不能保证满足条件,这时我们便需要使用断点。

断点虽然没有Gotocursor灵活,但有一个Gotocursor无法取代的优势便是它可以设置中断条件。

使用AddBreakpoint选项(Ctrl+F8)可以在当前光标处设置一个断点,Breakpoints选项可以编辑断点条件。

要注意的是,断点的设置会大大降低程序执行效率,因此调试完毕以后一定要记得清除所有断点。

Evaluate/modify选项(Ctrl+F4)用于临时观察修改表达式的值,如果在调试过程中,需要临时观察或修改某个表达式的值,则可以打开Evaluateandmodify窗口进行操作。

这个方法尤其适用于对过程(函数)的调试,我们可以先用Gotocursor执行到某个过程(函数)的开头,然后使用Evaluate/modify选项改变参数的值用于检查不同情况下这个过程(函数)的执行结果。

但是要注意,一个过程(函数)通常不是孤立的,它经常需要使用某些全局变量,因此仅改变参数而不改变其他全局变量的值有可能是非法的。

所以需要特别的小心,避免出现这样的情况。

Callstack选项(Ctrl+F3)用于显示当前堆栈状态,这特别适用于对递归算法的调试。

我们可以利用它察看每层参数的传递情况。

不过一般来说参数的传递不易出错,因此该选项的用处并不是很大。

四、调试的一般过程

对于一道题我们一般按照以下流程图进行调试:

这只是针对一般情况而言,在具体调试时,可以改变某些步骤,比如说在发现某个模块有错误改正以后,可以不返回到静态查错阶段,而是继续该模块的查错。

这要视具体情况而定。

下面我们对各个步骤作详细的叙述。

1、静态查错

静态查错是指不执行程序,仅根据程序和框图对求解过程的描述来发现错误。

由于有些错误运行时很难查出,但在静态查错中却容易发现,比如说前面说到的书写错误。

因此,静态查错往往是不可忽视的审查步骤。

静态查错一般顺序为先查程序局部,后查程序整体。

查局部主要是独立地检查各个子模块的功能是否按照算法要求实现,查整体主要是检查各个局部之间的接口是否吻合,是否缺少某些功能。

在静态查错阶段,我们可以有针对性地检查上面所列举的错误类型。

在这个阶段查出的错误越多,节省的调试时间也就越多。

2、动态查错

静态查错能够查出错误,但无法保证没有错误,因为这里有一个人为的因素在里面,只要一不小心,就可能漏掉一个错误。

因此,我们需要动态查错与之相结合来找出遗漏的错误。

与静态查错相对地,动态查错是指通过跟踪程序的执行过程,核对输出结果来发现错误。

动态查错的技巧可分为两大部分:

测试用例的设计和测试的方法。

我们先来讲测试方法,

动态查错的测试方法可以按照两种标准进行分类。

①按照测试用例的设计依据的不同,可分为黑箱测试法和白箱测试法。

只知程序的输入与输出功能,而不知程序的具体结构,通过列举各种不同的可能情况来设计测试用例,通过执行测试用例来发现错误的测试方法叫做黑箱测试法。

已知程序的内部结构和流向,根据程序内部逻辑来设计测试用例,通过执行测试用例来发现错误的测试方法叫做白箱测试法。

在竞赛中我们常常使用两者结合的方法进行测试。

在进行底层模块测试的时候可以使用白箱测试法,通过专门的测试条件和测试数据来考虑程序在不同点上的状态是否符合预期的要求。

在总体调试的时候则可以使用黑箱测试法,脱离程序内部结构来考察对于不同情况下的测试数据程序是否能够正确出解。

对于中间模块,可以用黑箱,也可以用白箱,或是两者兼用,具体要看适应那种测试法。

一般说来,结构复杂的模块使用黑箱测试法,结构简单的使用白箱测试法。

最后要说的是,由于白箱测试法测试用例设计比较困难,并且现在信息学竞赛都采用黑箱测试法进行测试,所以我们在竞赛时间紧张的情况下,可以一律采用黑箱测试法,这样效率比较高。

②按照测试顺序的不同,可分为由底向上测试和从顶向下测试。

由底向上测试是先测最底层的模块,然后依次向上测试,最后测试主模块。

从顶向下测试刚好与之相反,先测主模块,然后按照从顶向下设计的顺序依次测试各个模块。

为了加快调试的速度,建议采用从顶向下的测试顺序,只在发现某个模块有错时才进入下一层调试,这样对迅速定位错误也有很大帮助。

在运用这些测试方法时,我们要注意哪些问题呢?

首先,对自己所编的程序的结构、模块的功能一定要了如指掌。

采用从顶向下的测试方法时,经常是一个模块还没有测试完,就转到了下一个模块,因而特别容易忘记和疏漏。

如果对程序结构心中没有概念,就很容易被弄糊涂。

又如果对模块的功能不是很清楚,则难以判断模块执行结果的对错,从而无法准确确定错误所在。

其次,测试需要有条理地进行。

坚持使用同一个测试用例直到输出正确为止;在一个模块没有测试完毕时,不要进行下一个模块的测试,除非这个模块是当前模块的子模块且在当前模块的测试中发现这个子模块有错。

最后,在每次修改了源代码之后一定要把已经测过的所有测试用例再测一遍,以防产生新的错误。

讲完了测试方法,我们再来讲讲测试用例的设计。

黑箱测试法的测试用例是根据输入数据的不同情况来设计的。

由于一道题的输入数据可以千变万化,因此黑箱测试法不可能测遍所有的情况。

如有这样一道题,已知程序要求输入两个LongInt类型的整数X、Y,输出的是它们的和。

X共有2^32个不同值,Y也有2^32个不同值,这样不同的输入数据共有2^32*2^32=2^64种不同情况。

假设执行一遍程序要1毫秒,那么要测遍所有的不同情况大约需要五亿年,显然是不可能做到的。

白箱测试法的测试用例是根据程序内部逻辑来设计的。

要完全测试整个程序,测试用例就必须覆盖程序的所有执行路径,这通常也是不可能的。

例如,有一程序的流程图如下:

其中从a到b有五条路径,再外套循环20次,根据乘法原理,执行一遍程序可能经过的不同路径共有5^20≈10^14条。

假如程序执行一遍从a到b要0.1秒,那么测试完所有路径就需要一百多万年,这显然也是不可能的。

由上面两例可以看出,动态查错和静态查错一样,只能发现某些错误的存在,而不能证明错误的不存在。

所以,我们在设计测试用例的时候,需要考虑测试用例的经济性。

所谓经济性是指用尽可能少的测试用例来覆盖尽可能多的情况,对于黑箱测试法来说,就是要包括尽可能多的不同的输入数据类型,对于白箱测试法来说,就是要覆盖尽可能多的执行路径。

考虑到在竞赛中的测试以黑箱测试法为主,我们仅讨论黑箱测试法的用例设计。

常用的设计方法有等价分类法、边界分析法和错误推测法等等。

①等价分类法

所谓等价分类法就是根据程序功能将输入的数据划分成若干个等价类,然后考虑数据选择,设计出测试用例,以达到测试目的。

有这样一道题:

输入三个整数表示一个三角形的三条边长。

要求输出能否构成一个三角形,如能则输出是等腰,等边还是既非等边又非等腰三角形。

对于这道题,我们运用等价分类法,根据三角形是否合理可以将输入数据分成两个等价类:

合理三角形和不合理三角形。

对于合理三角形,我们可以继续将该等价类分为等腰三角形和既非等边又非等腰三角形,而对于等腰三角形,还可以分为单纯的等腰三角形和等边三角形。

这样我们通过不断地等价分类最后共可以设计出四种测试用例。

另外还可以根据输入数据的不同排列方式来分类。

但如果你的程序是先排序再进行判断,而且能够保证排序正确性,就没必要使用这种分类方法。

②边界分析法

程序运行出错常常发生在边界状态,所以在测试中我们常首先根据程序的功能确定边界情况的数据变化,以便设计测试用例。

这种对边界状态进行分析,以设计测试用例来测试程序的方法称为边界分析法。

边界值的选择要根据题目实际情况有针对性地,有一定创造性地进行。

一般来说,可考虑如下几种情况:

㈠恰好在边界附近,且欲越界的数据;

㈡取最大或最小值,最多或最少值加减1的数据;

㈢循环或迭代的初始值与终值数据;

㈣有序集合的第一个或最后一个数据元素;

㈤零点附近的元素;

㈥最大误差值的数据;

㈦数据量最大或最小的数据

㈧计算量最大或最小的数据

仍然使用上面那道三角形的题目作为例子,有如下三个测试数据:

a、两边之和等于第三边;

b、三个数中包含0;

c、三个数均为0;

a属于第㈠种情况,b、c属于第㈤种情况,这就是边界值分析法的应用。

边界分析法是很重要的一种方法,是一般测试中必不可少的。

它比较精炼,又容易发现错误。

问题在于有些程序的边界情况是极其复杂的,有时甚至不存在。

比如说让你把10个数从大到小排序输出,因为算法只与两个数之间的大小关系有关,与每个数的数值大小无关,数的总数也是固定的,这时就不存在边界情况,也就无法使用边界分析法来设计测试用例。

③错误推测法

错误推测法实际上是利用了黑箱白箱结合的思想,根据自己设计的程序来找出容易出现错误的地方,然后有针对性地设计测试用例。

例如在一个有许多If-Then-Else语句嵌套的地方,就很容易出现错误,而一般的测试用例很难找出这种错误。

这时错误推测法就能派上大用场,对于这一系列的If-Else语句,设计出覆盖不同路径的测试用例,从而检验程序的正确性。

至于具体测试时选用哪种方法,我们常用的策略是首先用边界分析法,再用等价分类法加以补充。

最后对于程序中结构复杂的部分重点使用错误推测法进行测试。

当然在时间允许的情况下,应该使用更多的测试数据来覆盖更多的功能。

3、跟踪调试

跟踪调试通常是一件繁琐的事。

它需要选手的耐心和细心。

选择哪些变量进行跟踪也是至关重要的,准确的变量选择可以起到事半功倍的效果。

一般来说,首先要跟踪的是存放输入数据的变量,尤其对于那些需要对输入数据进行一定处理的题目来说更是如此,输入数据不正确,即使是正确的程序也会与答案不符合;其次是那些频繁用到的全局变量,这些变量往往贯穿于整个程序中,一旦某处出错,会影响到其他模块的正确性,由此造成定位错误。

出错的地方没有测试,正确的地方反而反复测试。

因此对于这些全局变量的变化要密切加以关注,不可放过任何错误;再次就是那些循环变量了,跟踪循环变量可以准确地得知程序的执行进度,从时间上把握错误所在;最后其他的变量则要根据实际情况加以选择,对使用较多的变量应优先加以跟踪。

另外Watch窗口的大小位置也要合理安排,使得在跟踪过程中重要的变量的变化情况能够一目了然,从而免去许多不必要的窗口切换,节省时间。

五、总结

在信息学竞赛中,程序效率高低并不是首要的。

不正确的程序效率再高,也是等于零。

效率低的程序只要正确,总是能拿到一点分数。

因此调试水平的高低,很大程度上也反映了一个选手整体水平的高低。

总的来说,程序的调试主要靠的还是经验。

经验越丰富,调试效果也就越好。

因此我们平时训练时决不能只注意对思路算法的训练,还应该注意训练自己的调试水平。

特别声明:

1:

资料来源于互联网,版权归属原作者

2:

资料内容属于网络意见,与本账号立场无关

3:

如有侵权,请告知,立即删除。

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 表格模板 > 合同协议

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2