第11篇 ACM/ICPC竞赛之调试
在写程序时,调试程序也是一个重要的环节。怎样才能够更有效地调试程序,发现并修正错误呢?
1、调试中的输入输出
为了调试程序,我们可能需要反复执行程序,也就需要反复输入相同或不相同的测试数据。如果每次调试运行时都是以手工的方式输入测试数据,相信很多人都会觉得不胜其烦。其实我们可以用一些辅助的手段来简化这个过程。
方法一:使用剪贴板
可以将输入数据预先写好(用记事本、开发环境的编辑器或随便什么能够录入的东西),再将输入数据复制到剪贴板上(也就是说我们通常所说的复制操作)。在调试运行时,就可以直接将输入数据粘贴上去,不需要手工输入,这对于反复调试同一组测试数据尤其方便。
方法二:使用重定向
使用剪贴板对于多组测试数据或者比较长的测试数据就会显得不那么好用了。而使用输入输出的重定向则会更方便。
输入输出重定向是在终端窗口下的一种命令行功能,在命令行上可以用“<”表示输入重定向,在“<”后跟随输入文件名,则程序将从指定的输入文件中获取输入数据,而不再从键盘读入数据。也可以用“>”表示输出重定向。在“>”后跟输出文件名,则程序产生的标准输出将写入指定的输出文件中,而不是显示在屏幕上。
我们可以预先将输入数据存到文本文件中(如果有多组测试数据,可以存成多个文件),用重定向指定准备使用的输入数据。
例如,程序名为myprog,输入数据已经存到文件test.txt中,则在命令行下可以这样执行:
C:>myprog < test.txt
则程序会直接从test.txt中读取输入。如果想把输出结果也存到文件中(这在输出结果比较多的时候尤其有用,因为直接输出到屏幕上可能会来不及看到输出,或看不全所有的输出),例如,可以这样执行:
C:>myprog > test.out
这样我们就可以在执行后,用一个文本编辑器打开输出文件,慢慢阅读和分析输出结果。
如果把输入和输出的重定向结合起来,也可以这样执行:
C:>myprog < test.txt > test.out
2、输出调试信息
在调试时,很多同学往往首先想到的是使用开发环境所提供的调试功能:设置断点、单步执行、查看和修改变量,甚至改变程序的流程。不可否认,使用开发环境所提供的调试功能的确很方便,但当你过分依赖于这些集成工具时,你可能忽略了很多更有效的手段:仔细地分析、充分的信息。
当我们发现程序没有按照自己预期得那样工作时,不要急于跟踪甚至修改程序,而是应该首先仔细对程序的逻辑、语句、表达式进行检查和分析,尽可能使程序在表达上更简洁、更干净。如果实在难以发现问题所在,也不必急于借助于集成工具去跟踪程序的运行。早期的程序员在调试程序时经常会在程序中加入输出调试信息的语句或过程,用以观察程序的运行过程,分析程序的运行逻辑,这种调试手段即使在今天也仍然是非常有效的。
输出的调试信息要尽量容易阅读,格式清楚,在必要的时候,可以借助工具程序或自己编写的程序对输出信息进行处理,以帮助分析问题。
3、发现线索
调试的目的就是要分析错误发生的原因,寻找线索。盲目的调试只会浪费时间。
调试中的技巧很多,这里提出几条基本原则:
首先是要使错误可重现,要设法保证能够使错误按照自己的意愿重复出现。对于不知道什么时候会冒出来的错误,分析起来会困难得多!
缩小导致错误的输入,设法构造出最小的又能保证错误出现的输入,这样可以减少变化的可能性,使分析范围更集中。经常可以采用二分选择的方法来选择输入,就是舍掉一半输入,看看错误是否会出现,如果不出现,则选择另一半输入,如此反复,并不断缩小导致错误的输入。
4、构造测试数据和测试程序
在题目中所给出的测试样例只是一小组测试数据,这虽然通常是我们用来测试程序的第一组数据,但却是远远不够的。我们应该根据题意自行构造更多的测试数据,尤其是一些边界状态的测试数据(数据极大、数据极小、数据量极多、数据量极少、预期出现极端结果等情况)。
边界测试数据可以用于检查程序中是否存在边界错误,设计有缺陷的程序,在处理边界测试数据时往往容易暴露出错误。但如果没有发生明显的运行错误,就需要对结果的正确性进行验证。
有些测试数据可以通过手工计算求出结果,再与程序的计算结果相对比,而也有些问题,可以通过构造测试程序来进行验证。
测试程序通常是用确定可靠的算法编写的解题程序,但不须考虑时间和空间的消耗,用测试程序对测试数据进行求解,用计算结果与待测试程序的计算结果进行对比。
以题1041--纯素数问题为例,我们可以用最简单的穷举法进行求解,也许这样的解法是不被接受的,因为效率太低,但这个解法却可以用作我们的测试程序,甚至——有同学索性在本地先用这个程序把结果算出来,再写一个程序直接输出结果——居然也被接受了!