功能丰富的Perl:轻松调试Perl的技巧

  错误所带来的麻烦
  软件开发人员通常都低估了软件测试的重要性。这一现象的根本原因很简单:处理错误很困难!因为错误往往暴露了代码的根本缺陷,所以有时候开发人员甚至会为了几个错误而从头开始重新编写项目的主要部分。
  我认为,调试如此重要,以至于至少要为其分配整个项目 30% 的时间。额外的调试时间将导致更好的产品。另一方面,如果为了更快地推出软件而缩短调试时间,那么在软件生成后,您将花上双份的时间来修复那些稍后暴露出的问题。
  有三种基本类型的错误:编码错误、文档错误和需求错误。需求错误通常由于需求不严密或缺少需求而导致。文档错误存在于手册或联机帮助中。编码错误是由程序员在实现需求时的错误而引起的。不幸的是,需求错误和文档错误不在本文范围之内,因此,我们只好只讨论如何“检测”、“解决”和 “修复”编码错误了。
  调试的基本概念
  我们已经将编码错误定义成程序员在实现需求时产生的错误。编码错误会导致不正确的程序行为(偏离需求的行为)。因此,程序员在编写或调试程序之前首先应该知道的是程序需求。
  调试与狩猎没什么不同。第一步是检测错误(通过观察错误的行为并确认其模式)。在这个阶段,错误只是一些症状。
  第二步是解决错误。因为必须要在源代码中消除错误,所以,应该有一个精通程序的人来检查错误,并知道这些错误的根本原因。如果代码理解起来更容易,并且现在的代码没有比当初错误版本中的代码更多,则您可能做对了。
  第三步,也是最后一步,是修复错误(请注意“修复”与“解决”是有区别的)。调试程序将源代码更改放入“现场”的生产过程,然后检查它是否正确。如果代码不正确,则表明您没有解决错误,甚至更糟糕的是,可能还引入了新的错误。既然解决错误的目的不应该是引入新错误,请确保在解决错误之后修复每个错误。
  要确保迅速找到错误并很好地理解它们,您应该对调试过程中程序使用模块和类在每个主要分支处的操作非常清楚。当然,这要求您对编写代码所用的语言(在我们的示例中是 Perl)有深入的了解。因为存在所有这些需求,所以很难找到好的软件测试人员。
  Perl 调试器
  Perl 程序员的第一个资源是 Perl 所带的调试器。如您所见,着手使用该调试器是非常容易的。
  用调试器运行一个脚本
  perl -d program.pl
  Perl 调试器自带帮助('h' 或 'h h' 分别用于详细和简短的帮助屏幕)。perldoc perldebug 页面(在命令提示窗口输入 "perldoc perldebug")有更完整的 Perl 调试器描述。
  现在,让我们从一个有错误的程序着手,看一下 Perl 调试器是如何工作的。首先,它将尝试打印一个文件的前 20 行。
  buggy.pl
  #!/usr/bin/perl -w
  use strict;
  foreach (0..20)
  {
  my $line = ;
  print "$_ : $line";
  }
  当它独自运行时,buggy.pl 将失败,并给出消息:"Use of uninitialized value in concatenation (.) at ./buggy.pl line 8, line 9."。更神秘的是,它还自己在一行上打印 "9:" 并等待用户输入。
  那意味着什么?如果调用了 Perl 调试器,您可能已经找到问题所在了。
  首先,让我们证实这个错误是可以重复的。我们将在第 8 行设置一个操作来打印发生错误的 $line,然后再运行程序。
  buggy.pl 调试器命令
  perl -d ./buggy.pl buggy.pl
  Default die handler restored.
  Loading DB routines from perl5db.pl version 1.07
  Editor support available.
  Enter h or `h h' for help, or `man perldebug' for more help.
  main::(./buggy.pl:5):
  foreach (0..20)
  main::(./buggy.pl:6):
  {
  DB use Data::Dumper
  DB a 8 print 'The line variable is now ', Dumper $line
  装入了 Data::Dumper 模块,以便自动操作可以使用一种美观的输出格式。自动操作被设置成每次到达第 8 行时都执行打印语句。现在,让我们演示一下。
  buggy.pl 调试器命令,第 2 部分
  DB c
  The line variable is now $VAR1 = '#!/usr/bin/perl -w
  ';
  0 : #!/usr/bin/perl -w
  The line variable is now $VAR1 = '
  ';
  1 :
  The line variable is now $VAR1 = 'use strict;
  ';
  2 : use strict;
  The line variable is now $VAR1 = '
  ';
  3 :
  The line variable is now $VAR1 = 'foreach (0..20)
  ';
  4 : foreach (0..20)
  The line variable is now $VAR1 = '{
  ';
  5 : {
  The line variable is now $VAR1 = ' my $line = ;
  ';
  6 :
  my $line = ;
  The line variable is now $VAR1 = ' print "$_ : $line";
  ';
  7 :
  print "$_ : $line";
  The line variable is now $VAR1 = '}
  ';
  8 : }
  The line variable is now $VAR1 = undef;
  Use of uninitialized value in concatenation (.) at ./buggy.pl line 8, line 9.
  9 :
  现在很清楚,没有定义行变量时就会出问题。而且,程序等待更多的输入。再按 11 次回车键产生了以下输出:
  buggy.pl 调试器命令,第 3 部分
  The line variable is now $VAR1 = '
  ';
  10 :
  The line variable is now $VAR1 = '
  ';
  11 :
  The line variable is now $VAR1 = '
  ';
  12 :
  The line variable is now $VAR1 = '
  ';
  13 :
  The line variable is now $VAR1 = '
  ';
  14 :
  The line variable is now $VAR1 = '
  ';
  15 :
  The line variable is now $VAR1 = '
  ';
  16 :
  The line variable is now $VAR1 = '
  ';
  17 :
  The line variable is now $VAR1 = '
  ';
  18 :
  The line variable is now $VAR1 = '
  ';
  19 :
  The line variable is now $VAR1 = '
  ';
  20 :
  Debugged program terminated.
  Use q to quit or R to restart,
  use O inhibit_exit to avoid stopping after program termination,
  h q, h R or h O to get additional info.
  DB
  到现在为止已经很清楚了,由于即使在不存在行的情况下,程序仍无条件地等待 20 行的输入,所以程序会出错。修复就是要在从 filehandle 读取 $line 之后测试它:
  buggy.pl fixed
  #!/usr/bin/perl -w
  use strict;
  foreach (0..20)
  {
  my $line = ;
  last unless defined $line;# exit loop if $line is not defined
  print "$_ : $line";
  }
  如您所见,修复过的程序在所有情况下都可以正确工作!
  关于 Perl 调试器的结论
  Emacs 编辑器支持 Perl 调试器并使其更易于使用。您可以在 Emacs 中使用 Info(输入 M-x info)来阅读有关 GUD Emacs 的更详细信息。GUD 是与 Perl 调试器一起工作的全局调试方式(当在 Emacs 中编辑 Perl 程序时输入 M-x perldb)。
  只需少量工作就可以让 vi 系列的编辑器也能支持 Perl 调试器。有关详细信息,请参阅 perldoc perldebug 页面。有关其它编辑器的信息,请参考每个编辑器的文档。
  Perl 内置的调试器是一个强大的工具,可以执行比我们刚刚看到的简单用法复杂得多的任务。但它的确要求使用者具备大量 Perl 专门知识。正因为如此,我们现在要看一些简单些的工具,这些工具将更适合初级和中级 Perl 程序员。
  Devel::ptkdb
  要使用 Devel::ptkdb 调试器,首先得从 CPAN(请参阅下面的参考资料)下载它并将它安装在您的系统上。(某些用户可能还需要安装 Tk 模块,该模块也可以从 CPAN 获得。)就我个人看来,Devel::ptkdb 在 UNIX 系统(如 Linux)上最好用。(虽然在理论上 Devel::ptkdb 并不限于与 UNIX 兼容的系统,但是,我从未听说过有人成功地在 Windows 上使用 Devel::ptkdb。正如一句老话所讲:除了滑雪穿过旋转门之外,任何事都是可能的。)
  如果无法让系统管理员为您安装(例如,因为您自己就是系统管理员),可以尝试在命令提示行执行以下操作(可能需要以 root 身份执行这些操作):
  从 CPAN 安装 Devel::ptkdb
  perl -MCPAN -e'install Tk'
  perl -MCPAN -e'install Devel::ptkdb'
  如果是第一次运行 CPAN 安装例程,那么,在回答一些初始问题之后,将自动下载并安装适当的模块。
  可以用 ptkdb 调试器运行程序,如下所示(使用我们以前的 buggy.pl 示例):
  使用 Devel::ptkdb
  perl -d:ptkdb buggy.pl buggy.pl
  要阅读 Devel::ptkdb 模块的文档,请使用命令 "perldoc Devel::ptkdb"。我们在本文中使用版本 1.1071。(虽然更新的版本可能随时问世,但它们与我们正在使用的版本应该没有很大的不