给定一些变量来测试定义性,如何(轻松地)找出未定义的变量?

Given a number of variables to test for definedness, how to (easily) find out the one which was left undefined?

今天看到这段代码:

if (    not defined($reply_address) 
     or not defined($from_name) 
     or not defined($subject) 
     or not defined($date) )
{ 
  die "couldn’t glean the required information!"; 
}

(Jeffrey Friedl,“精通正则表达式”,第 59 页,第 3 版。)

然后我想“我怎么知道哪个变量失败了?”

当然,如果只有4个变量要测试,如上例,可以想出:

if ( not defined $reply_address ) 
{ 
  die "$reply_address is not defined" 
}
elsif ( not defined $from_name )
{
  die "$from_name is not defined"
}
elsif ...

但是如果有14个变量呢?还是 40...? 还需要全部过一遍,逐个手动测试?

有没有更短、更“神奇”的方法来判断哪个变量未定义?

您可以创建一个 table 来稍微简化一下:

use strict;
use warnings;

my $reply_address = "xyz";
my $from_name;
my $subject = "test";
my $date;

my @checks = (
    [$reply_address, '$reply_adress'],
    [$from_name, '$from_name'],
    [$subject, '$subject'],
    [$date, '$date'],
);

for my $check (@checks) {
    if (not defined ${$check->[0]}) {
        die $check->[1] . " is not defined";
    }
}

可以用字符串求值来完成:

use strict;
use warnings;

my ($reply_address, $from_name, $subject, $date) = ('', '', undef, '');

for my $var (qw(reply_address from_name subject date)) {
    my $defined;
    eval "$defined = defined $$var";
    die "eval failed: $@" if $@;
    die "$$var is not defined" unless $defined;
}

你可以随心所欲地使用symbolic references, though using them is generally not a great idea, and it can only be done with package variables, not lexically scoped variables (and lexically scoped variables are preferred to package variables -- see this answer来简单比较两者。

#!/usr/bin/env perl

use strict;
use warnings;

use 5.014;

our($foo1) = 1;
our($bar1) = undef;
our($baz1) = 3;

foreach my $name (qw(foo1 bar1 baz1)) {
    {
        no strict 'refs';
        my($value) = $$name;
        warn "$name: is not defined" unless defined $value;
        say "$name: <$value>";
    }
}

出于说明目的,使用 warn 而不是 die

</tmp> $ ./test.pl
foo1: <1>
bar1: is not defined at ./test.pl line 16.
Use of uninitialized value $value in concatenation (.) or string at ./test.pl line 17.
bar1: <>
baz1: <3>

您也可以使用通用代码循环遍历所有变量来检查它们:

#!/usr/bin/env perl

use strict;
use warnings;

use 5.014;

my($foo2) = 1;
my($bar2) = undef;
my($baz2) = 3;

foreach my $vardef (["foo2", $foo2], ["bar2", $bar2], ["baz2", $baz2]) {
    my($name) = $vardef->[0];
    my($value)  = $vardef->[1];

    warn "$name: is not defined" unless defined $value;
    say "$name: <$value>";
}

给出类似的输出:

foo2: <1>
bar2: is not defined at ./test.pl line 29.
Use of uninitialized value $value in concatenation (.) or string at ./test.pl line 30.
bar2: <>
baz2: <3>

最后,如果您可以设法将变量放入散列中,则可以遍历散列的键并以这种方式测试它们:

#!/usr/bin/env perl

use strict;
use warnings;

use 5.014;

my($vars) = {
    foo3 => 1,
    bar3 => undef,
    baz3 => 3,
};

foreach my $name (sort keys %$vars) {
    my($value)  = $vars->{$name};

    warn "$name: is not defined" unless defined $value;
    say "$name: <$value>";
}

我把 sort 放在那里是因为我喜欢确定性行为...

bar3: is not defined at ./test.pl line 42.
Use of uninitialized value $value in concatenation (.) or string at ./test.pl line 43.
bar3: <>
baz3: <3>
foo3: <1>

如果测试真的像die if ! defined那么简单,那么我可能会把它们列出来:

#!/usr/bin/env perl

use strict;
use warnings;

use 5.014;

my($foo4) = 1;
my($bar4) = undef;
my($baz4) = 3;

die qq([ERROR] $foo4 not defined\n) unless defined $foo4;
die qq([ERROR] $bar4 not defined\n) unless defined $bar4;
die qq([ERROR] $baz4 not defined\n) unless defined $baz4;

这给了我们:

[ERROR] $bar4 not defined

最后一种方法非常简单明了。如果测试没有这么简单,那么我会采用第二种方法。如果您担心这种性质的 40(甚至 14)个检查列表,那么我会看一下设计。

另请参阅此 PadWalker 代码示例,了解第一个选项的非常复杂的版本,但允许词法范围的变量。