5

我希望这是我做错了的直截了当的事情。我在网上看到了一些关于“变量自杀”的东西,看起来不错,但它是针对旧版本的,我在 5.10.1 上。

无论如何 - 我声明的一个变量 - $RootDirectory - 突然失去了它的价值,我不知道为什么。

这是重现问题的脚本。当我在调试模式下运行脚本(perl -d)时,我可以让它在第 21 行和第 26 行打印出 $RootDirectory。但它在第 30 行消失了。

use strict;
my $RootDirectory; 
my @RootDirectories; 

@RootDirectories = (
   'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\'
   ,'c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\'
   ,'c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\'
   );

foreach $RootDirectory (@RootDirectories) { 
   # $RootDirectory = 'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\';
   # print ' In foreach ' . $RootDirectory. "\n";
   RunSchema ();
} 

exit(0);

sub RunSchema() { 
   # print ' In RunSchema ' . $RootDirectory. "\n";
   CreateTables ();
} 

sub CreateTables() { 
   # print ' In CreateTables ' . $RootDirectory. "\n";
   SQLExecFolder ('tbl');
} 

sub SQLExecFolder() { 
   print ' In SQLExecFolder ' . $RootDirectory. "\n";       # Variable $RootDirectory value is gone by now
} 

编辑感谢所有评论!我想现在我会使用“我们的”关键字,它看起来效果很好——谢谢内森。还要感谢有关使用警告的工具 - 我想我已经卖掉了那个!

继续让我感到困惑的是,为什么当我执行调试模式 (perl -d) 并单步执行代码时,执行“p $RootDirectory”时,我在第 21 行和第 26 行得到了预期的输出,但在第 30 行却没有。如何第 30 行的情况是否有所不同?

另外,我很欣赏关于将 $RootDirectory 作为函数参数传递的最佳实践的评论。我想避免这种情况,因为在这之后我有很多函数——即 RunSchema 调用调用 SQLExecFolder 的 CreateTables。所有这些都必须传递相同的参数。在这种情况下它仍然有意义,还是有更好的方法来构建它?

4

7 回答 7

8

内森说的是对的。除此之外,你为什么不传递价值?无论如何,这是更好的做法:

foreach $RootDirectory (@RootDirectories) { 
   # $RootDirectory = 'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\';
   # print ' In foreach ' . $RootDirectory. "\n";
   RunSchema ($RootDirectory);
} 

sub SQLExecFolder { 
   my $RootDirectory = shift;
   print ' In SQLExecFolder ' . $RootDirectory. "\n";
} 
于 2010-03-15T20:07:18.423 回答
5

您在$RootDirectory循环中声明为循环变量foreach。据我了解,这意味着它的值被定义local为循环,并且它的值在循环结束时恢复为之前的值。

在您的情况下,该变量从未被分配,因此在循环结束时它返回到其先前的undef.

编辑:实际上,问题在于$RootDirectory用 声明my,因此在其他范围内未定义。在函数中RunSchema,变量是未定义的,无论.CreateTablesSQLExecFolderforeach

如果您希望为strictness 声明变量,但希望它是全局的,请$RootDirectory使用以下命令声明our

our $RootDirectory;

编辑:话虽如此,使用全局变量并不总是一个好主意。您最好按照其他人的建议将变量作为参数传递给函数。

于 2010-03-15T20:04:51.030 回答
5

其他人已经正确回答了你的问题。我只想强调您应该添加use warnings;到您的代码中。它会为您的问题提供线索,并提醒您注意另一个潜在危险。

于 2010-03-15T20:19:52.993 回答
4

foreach变量是特殊的——它是循环的局部变量。

如果变量以关键字 my 开头,则它是词法范围的,因此仅在循环中可见。否则,该变量隐含地是循环的局部变量,并在退出循环时恢复其先前的值。如果该变量以前用 my 声明过,它会使用该变量而不是全局变量,但它仍被本地化到循环中。这种隐式本地化仅发生在 foreach 循环中。

请看这里

于 2010-03-15T20:06:01.910 回答
2

RE: 什么时候使用全局变量?

全局变量是有风险的,因为它们可以随时被访问它们的代码的任何部分更改。此外,很难跟踪更改发生的时间和地点,这使得跟踪修改的意外后果变得更加困难。简而言之,每个全局变量都会增加使用它的子程序之间的耦合。

什么时候使用全局变量有意义?当收益大于风险时。

如果您的大多数或所有子例程都需要许多不同的值,那么似乎是使用全局变量的好时机。您可以简化每个子例程调用,并使代码更清晰,对吗?

错误的。在这种情况下,正确的方法是将所有这些不同的变量聚合到一个容器数据结构中。所以不是foo( $frob, $grizzle, $cheese, $omg, $wtf );你有foo( $state, $frob ); Where $state = { grizzle => $grizzle, cheese => $cheese, omg => $omg, wtf => $wtf };

所以现在我们有一个变量要传递。所有这些子调用都简单得多。然而,即便如此,这还是很繁重的,你仍然想清理每个例程中的额外参数。

此时,您有几个选择:

  1. 使其成为$state全局并直接访问它。
  2. 制作$state成一个配置对象并使用方法来控制对属性的访问。
  3. 将整个模块做成一个类,并将所有状态信息存储在一个对象中。

选项 1 对于例程很少的小型脚本是可以接受的。难以调试错误的风险很小。

当模块中的不同例程之间没有明显的关系时,选项 2 是有意义的。使用全局状态对象会有所帮助,因为它减少了访问它的代码之间的耦合。添加日志记录以跟踪对全局数据的更改也更容易。

如果您有一组密切相关的函数对相同的数据进行操作,则选项 3 效果很好。

您的示例代码似乎是选项 3 的一个很好的候选者。我创建了一个名为的类MySchema,并且在特定目录上操作的所有方法现在都是方法。调用对象携带它需要的数据。

现在我们有了漂亮、干净的代码并且没有全局变量。

use strict;
use warnings;

my @directories = (
   'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\',
   'c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\',
   'c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\',
);

for my $schema ( make_schemata(@directories) ) {

    $schema->run;

}

sub make_schemata {
    my @schemata = map { MySchema->new( directory => $_ } @_;

    return @schemata;
}


BEGIN {
    package MySchema;

    use Moose;

    has 'directory' => (
        is => 'ro',
        isa => 'Str',
        required => 1,
    );

    sub run { 
       my $self = shift;

       $self->create_tables;
    } 

    sub create_tables { 
       my $self = shift;

       $self->sql_exec_folder('tbl');
    }

    sub sql_exec_folder {
        my $self = shift;

        my $dir = $self->directory;

        print "In SQLExecFolder $dir\n";
    }
    
    1;
} 

作为奖励,可以删除 BEGIN 块中的代码并将其放置在单独的文件中以供另一个脚本重用。它只需要一个完整的模块就是它自己的名为MySchema.pm.

于 2010-03-16T15:45:01.323 回答
2

foreach 循环中的迭代器变量总是本地化到循环中。请参阅perlsyn中的 foreach 部分。您可以将其作为参数传递给子例程。

于 2010-03-15T20:07:09.803 回答
0

不坏的努力。这里有一些小的改进,一个“修复”是将变量作为函数参数传递给子程序,因为$RootDirectory变量的范围(即限制)在foreach循环内。通常,为了明确哪些变量正在被各种子例程传递和/或访问,这也被认为是一种好的做法。

use strict;
use warnings;

sub RunSchema() {
   my $root_dir = shift;
   CreateTables($root_dir);
}

sub CreateTables() {
   my $root_dir = shift;
   SQLExecFolder('tbl', $root_dir);
}

sub SQLExecFolder() {
   my ($name, $root_dir) = @_;
}
######################################################


my @RootDirectories = qw(
   c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\
   c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\
   c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\
);

foreach my $RootDirectory (@RootDirectories) {
   # print ' In foreach ' . $RootDirectory. "\n";
   RunSchema($RootDirectory);
}

exit(0);
于 2010-03-15T20:13:15.717 回答