我在大学学计算机科学的时候,学了很多关于如何写程序的,但是据我所知,至今没人教我们怎么调试。 我们听说过这个创造新事物的美好世界,但是没人告诉我们大多数时间里,我们需要理解别人的代码。

事实证明在写程序时,我们花费很多时间在尝试理解我们(或者他人)所写的代码以及为什么工作异常。 这时间远多过首次把代码写出来。

什么是调试?

执行程序前,一切看起来很好。

执行程序后,某些东西变成出乎意料、不正确的状态。

任务就是找出在哪一点出了什么问题并改正它。

什么是编程,什么是bug?

基本上,编程是通过在变量里搬动数据来一点点改变世界。

在程序的每一步,我们改变程序中某变量的一些数据或者“现实世界”的一些东西。(比如磁盘里或者屏幕上)

当你写程序时,你每一步安排:什么值应该赋给哪个变量。

bug就是当你认为你把值X放入某个变量,而实际上值Y被放入了。

在某一点,通常是程序结束,你会发现它因为程序打印了不正确的值。

程序执行时,它可能导致出现警告或者程序异常终止。

怎么调试?

最直接的调试办法是运行程序,在每一步检查是否所有的变量包含了期望的值。你可以通过使用调试器或者在程序中嵌入print语句后,来检查输出。

Perl带了很强大的命令行调试器。我建议学一下,不过刚开始可能会比较恐怖。 我准备了一个视频来演示Perl内置调试器的基本命令

集成开发环境,诸如KomodoEclipsePadre, the Perl IDE 带了一个图形界面的调试器。有时间我也会准备一个他们的视频。

打印语句

很多人还在用老掉牙的办法去在代码里加入打印语句。

如果一种语言编译和构建很花时间,打印语句就是一个很坏的办法去调试代码。 Perl不是这样,即便是很大的应用程序也可以在几秒钟编译并开始运行。

添加打印语句时,需要记住在值周围加上分隔符。这样可以发现值之前或之后有空白可能导致问题的情况。 不加分隔符的话,很难发现他们:

标量值可以如此打印:

print "<$file_name>\n";

这里小于号和大于号只是使读者易于辨识变量的真实内容:

<path/to/file
>

如果打印如上结果,你会很快发现$file_name变量后有个拖尾的新行符。或许是你忘了调用chomp

复杂数据结构

我们甚至还没学标量,但是让我来往前跳一下来为您展示如何打印复杂数据结构的内容。 如果你在Perl教程中读到这一部分,你可以希望跳到下一章节,之后再回来。现在这不是太大问题。 否则,继续读下去。

对于复杂数据结构(引用,数组和哈希),你可以使用Data::Dumper

use Data::Dumper qw(Dumper);

print Dumper \@an_array;
print Dumper \%a_hash;
print Dumper $a_reference;

这会打印类似如下内容,它有助于了解变量的内容,但是只是显示了一个通用的变量名比如$VAR1$VAR2

$VAR1 = [
       'a',
       'b',
       'c'
     ];
$VAR1 = {
       'a' => 1,
       'b' => 2
     };
$VAR1 = {
       'c' => 3,
       'd' => 4
     };

我建议像这样加一些代码打印出变量名:

print '@an_array: ' . Dumper \@an_array;

这将得出:

@an_array: $VAR1 = [
        'a',
        'b',
        'c'
      ];

或者像这样使用Data::Dumper:

print Data::Dumper->Dump([\@an_array, \%a_hash, $a_reference],
   [qw(an_array a_hash a_reference)]);

得出

$an_array = [
            'a',
            'b',
            'c'
          ];
$a_hash = {
          'a' => 1,
          'b' => 2
        };
$a_reference = {
               'c' => 3,
               'd' => 4
             };

还有更漂亮的办法打印数据结构,但是这里Data::Dumper已经足够满足我们的需求,而且每个perl版本都预装了。 我们以后会讨论其他方法。