
算法合集之《论程序的调试技巧》.doc
19页论程序的调试技巧【关键字】调试技巧、测试方法、测试用例设计【摘要】本文结合作者自身经验,对竞赛中程序的调试技巧做了详细的阐述和总结在介绍了编程中常见的错误类型和集成环境的调试工具之后,给出了一般调试流程,并着重讲述了其中的动态查错技巧,做了一定的归纳最后通过一个调试实例来体现本文所论述的调试技巧的具体应用【正文】一、 程序调试的必要性程序设计过程中,错误是在所难免的虽然有些程序员认为一个程序可以做到完美无瑕,但实际情况却并非如此,不然就不会有人对Windows怨气冲天了尽管信息学竞赛中所编的程序从来不会像Windows那样庞大,最多也是仅仅几百K而已,但由于时间有限,选手们的程序难免有疏漏之处因此,调试就成了极其重要的一环如何在紧迫的时间内快速准确地发现并改正错误,正是本文所要讨论的问题二、 常见错误类型归纳《孙子兵法》云:“知己知彼,百战不殆对于程序调试者来说,程序中的错误就好比是敌人,如能准确把握敌人的情况,无疑是极为有利的下面我们就来对常见的一些错误类型进行归纳并给出解决方法1、 思路错误这要看是基本算法错误还是功能缺陷前者需要重写大部分代码,是否重写则根据时间是否充裕而定,后者只需增加一部分代码,再修改某些地方,这时应全面考虑,以防遗漏应该修改的地方。
2、 语法错误这个没什么可说的,作为一名信息学竞赛的选手,应该对自己选择的编程语言的语法了如指掌,具体在这里就不多讲了3、 书写错误这种错误令人十分头痛,一般的书写错误在编译时都能找出来,但如果你在表达式中用到变量j时误写成了i,不但编译程序找不出来,自己找时也由于两者样子比较相似,难以发现排除这种错误只能靠“细心”两字,具体可使用下面要介绍的静态查错法4、 输出格式错误由于现在信息学竞赛采用黑箱测试法,由于输出格式错误而导致失分的例子屡见不鲜一个标点,一个空格,都会导致最后的悔恨因此,在调试时先要核对输出格式,针对不同输出格式多设计几个测试用例,以防一失足成千古恨5、 其它编程时易犯的错误除了上面所说的错误类型外,其它就属于编程时在细节上考虑不周所造成的了下面仅列举其中一些较为隐蔽的错误只有靠平时不断总结积累,才能真正的做到“知己知彼”①变量未赋初值看下面的程序段For i:=1 to N Do If A[i]>Max Then Max:=A[i];WriteLn(Max);这个程序段的原意显然是要输出数组A中最大的数但由于它遗漏了将Max赋初值的语句,因此很可能会出现输出的数并不在数组A中的错误。
应该在过程开头添上一句Max:=-MaxInt;养成变量使用前先赋初值的习惯能预防许多较隐蔽的错误② 中间运算越界看下面这两句语句A:=1000;B:=A*A Div 100;其中A,B都是Integer类型按照我们的想法,1000*1000 Div 100=10000然而当我们察看B的值的时候,却发现B等于169原因是Pascal在进行编译时,总是先计算出A*A,把它放到一个中间变量中,然后再计算出最后结果放入B中而A*A超出了Integer的范围,这就是造成错误的根本原因要使Pascal能报告这类错误,只要打开编译开关Q即可对此类错误解决方法是使用强制类型转换,写成B:=LongInt(A)*A Div 100编译程序会自动把中间变量规定为LongInt类型,就不会越界了③ 局部变量与全局变量同名造成概念混乱这个实际上不能算错误,然而有许多错误正是因此而起一个常见的错误是当我们在过程中使用全局变量时,忘记了自己在该过程中还定义了一个同名的局部变量,从而使得实际的程序与我们的思路不一致;另一个常见的错误是局部变量忘记定义,在过程(函数)中实际上使用的是全局变量,而出现错误往往是在这个过程(函数)之外某个需要使用该全局变量的地方,这就增加了调试的难度。
因此,应该尽量避免使用同名变量④ If-Then-Else语句混乱Pascal对If-Then-Else语句的规定是:If-Then语句可以没有Else语句与之相匹配;Else语句总是匹配最近的If-Then语句这一点使得我们在使用嵌套的If-Else语句时容易出错如下面这个例子:If 条件a Then If 条件b Then 代码段b Else 代码段a我们的原意是让代码段a在条件a不成立时执行,但由于Else语句总是匹配最近的If-Then语句,因此这个Else是与If 条件b Then这个语句相匹配的,也就是说代码段a要满足条件a成立且条件b不成立时才会执行,与我们原意相去甚远解决方法是在需要的地方加一个空的Else,就如上面的例子,要在If 条件b Then语句后面加一个空的Else⑤ 实数比较出错在比较两个实数是否相等时,如果直接用等号,往往会造成错误这是浮点运算存在误差所造成的,解决办法是使用两数差的绝对值与一个相对极小量进行比较,一般说来如果abs(a-b)<1e-8,则可认为a=b三、 集成环境的调试工具对于一个战士来说,对自己手中的武器性能特点应该了如指掌对于程序调试者来说,调试工具就相当于武器,熟练掌握调试工具,充分发挥它的性能,对于迅速找出错误,加快我们的调试速度有着极大的帮助。
下面就对集成环境提供的调试工具做一些介绍调试时主要使用的两个菜单是Run和DebugRun菜单提供了各种程序执行方式,而Debug菜单提供了对变量的观察,修改以及断点等功能程序的执行方式有四种:1、 Run,运行整个程序(Ctrl+F9),该方式常用在总体测试上一般每一个测试用例都应先用该方式执行程序,如果输出答案正确就可以直接转到下一个测试用例,免去了不必要的时间即使发现错误也只不过比直接进入模块调试增加了一点点时间,是完全值得的2、 Step over,单步执行,把整个过程(函数)视为单步一次执行(F8),该方式常用在模块调试时期,可以通过观察变量在模块执行前后的变化情况来确定该模块中是否存在错误,也可以用来跳过已测试完毕的模块3、 Trace into,单步执行,对于过程(函数)进入到内部一步步执行(F7),该方式常用在底层调试时期,可以跟踪程序的每步执行过程它的优点是容易直接定位错误,缺点是调试速度较慢,尤其是当错误位于程序后部时所以一般是采用先用模块调试法尽量缩小错误范围,然后使用第4种执行方式和断点来快速跳过没有出现错误的部分,最后才是用该方式来逐步跟踪找出错误4、 Go to cursor,执行到光标处(F4),之所以把这种方式放在最后介绍,是因为这种方式的灵活度较大,不但可以一次执行一行,也可以一次执行多行;可以直接跳过过程(函数),也可以进入过程(函数)内部。
它有断点的定位能力强的优点,又比断点更加灵活正确适当地使用这种方式可以大大加快我们调试的速度,这需要靠丰富的调试经验可以参考后面的调试实例Debug菜单中最常用选项是Watch和Add Watch,这两个用于跟踪观察变量和表达式在程序执行过程中值的变化,这样就可以随时检查它们是否按照算法要求输出,是否符合正确答案大多数错误在调试时都可以只使用它们以及上面的四种执行方式被检查出来有的时候,虽然知道该模块有错,但一时无法找到错误所在,并且上面所讲的后三种执行方式都难以快速定位例如对于一个程序,我们需要它执行到某个语句并满足某个条件时停下来,Go to cursor只能保证执行到这个语句时停下来,却不能保证满足条件,这时我们便需要使用断点断点虽然没有Go to cursor灵活,但有一个Go to cursor无法取代的优势便是它可以设置中断条件使用Add Breakpoint选项(Ctrl+F8)可以在当前光标处设置一个断点,Breakpoints选项可以编辑断点条件要注意的是,断点的设置会大大降低程序执行效率,因此调试完毕以后一定要记得清除所有断点Evaluate/modify选项(Ctrl+F4)用于临时观察修改表达式的值,如果在调试过程中,需要临时观察或修改某个表达式的值,则可以打开Evaluate and modify窗口进行操作。
这个方法尤其适用于对过程(函数)的调试,我们可以先用Go to cursor执行到某个过程(函数)的开头,然后使用Evaluate/modify选项改变参数的值用于检查不同情况下这个过程(函数)的执行结果但是要注意,一个过程(函数)通常不是孤立的,它经常需要使用某些全局变量,因此仅改变参数而不改变其他全局变量的值有可能是非法的所以需要特别的小心,避免出现这样的情况Call stack选项(Ctrl+F3)用于显示当前堆栈状态,这特别适用于对递归算法的调试我们可以利用它察看每层参数的传递情况不过一般来说参数的传递不易出错,因此该选项的用处并不是很大四、 调试的一般过程对于一道题我们一般按照以下流程图进行调试: 调试开始 通过静态查错? Y 总体测试通过? Y N N 模块测试通过? Y 所有模块已经测试完毕? Y N 跟踪调试 修改错误,重新编码 测试用例完否? Y N 调试结束这只是针对一般情况而言,在具体调试时,可以改变某些步骤,比如说在发现某个模块有错误改正以后,可以不返回到静态查错阶段,而是继续该模块的查错。
这要视具体情况而定下面我们对各个步骤作详细的叙述1、 静态查错静态查错是指不执行程序,仅根据程序和框图对求解过程的描述来发现错误由于有些错误运行时很难查出,但在静态查错中却容易发现,比如说前面说到的书写错误因此,静态查错往往是不可忽视的审查步骤静态查错一般顺序为先查程序局部,后查程序整体查局部主要是独立地检查各个子模块的功能是否按照算法要求实现,查整体主要是检查各个局部之间的接口是否吻合,是否缺少某些功能在静态查错阶段,我们可以有针对性地检查上面所列举的错误类型在这个阶段查出的错误越多,节省的调试时间也就越多2、 动态查错静态查错能够查出错误,但无法保证没有错误,因为这里有一个人为的因素在里面,只要一不小心,就可能漏掉一个错误因此,我们需要动态查错与之相结合来找出遗漏的错误与静态查错相对地,动态查错是指通过跟踪程序的执行过程,核对输出结果来发现错误动态查错的技巧可分为两大部分:测试用例的设计和测试的方法我们先来讲测试方法,动态查错的测试方法可以按照两种标准进行分类① 按照测试用例的设计依据的不同,可分为黑箱测试法和白箱测试法只知程序的输入与输出功能,而不知程序的具体结构,通过列举各种不同的可能情况来设计测试用例,通过执行测试用例来发现错误的测试方法叫做黑箱测试法。
已知程序的内部结构和流向,根据程序内部逻辑来设计测试用例,通过执行测试用例来发现错误的测试方法叫做白箱测试法在竞赛中我们。












