9

我刚刚在将其他人的 API 与默认变量 $_ 结合使用时被抓到了

foreach (@rps_server_details) {
    @server_data = ();
    @server_data = split(/,/);
    @$esp_hosts = ();
    $filters{server_name} = $server_data[0];
    print "--->$_<--\n";
    $esp_hosts = $esp->get_hosts(fields => $fields, %filters) || die "$@";
    print "--->$_<--\n";

输出是:

--->igrid8873.someone.com,app_10<--
Use of uninitialized value in concatenation (.) or string at ./rps_inv_lookup.pl line 120.
---><--

指定我自己的循环变量而不是依赖 $_ 可以解决问题。

将 $_ 与其他人编写的 API 结合使用是不是太天真了?或者这是该 API 模块中的错误?

4

3 回答 3

9

这是 API 中的一个错误。如果您$_在函数中使用,则添加一个很重要

local($_);

在函数内部避免破坏调用者的$_,或者避免$_在库函数中使用以被其他人调用。

如果您可以将自己限制为 Perl 版本 > 5.9.1,那么您也可以使 $_ 词法化,这比local使用更容易理解

my $_;

但这将在早期版本的 Perl 上中断。

来自 man perlvar:

作为$_一个全局变量,这在某些情况下可能会导致不必要的副作用。从 perl 5.9.1 开始,您现在可以$_通过在文件或带有“my”的块中声明它来使用词法版本。此外,声明“ our $_”会恢复$_ 当前范围内的全局。

于 2010-10-29T18:09:53.133 回答
6

我会说它是:

  1. 违反了您的最佳实践(始终尽可能使用本地变量范围,并避免$_仅因为您遇到的问题而使用)

  2. 再加上API 中的一个错误,该错误是由同样违反最佳实践以及未按照perldoc perlvar所禁止的那样本地化特殊变量而local $_引起的。

除了 perldoc,该 API 还违反了 Perl 最佳实践(如 Conway 书中的规则):

第 5.6 节。本地化标点变量

如果您被迫修改标点符号变量,请将其本地化。

前面在“本地化”中描述的问题也可能会在您被迫更改标点符号变量中的值(通常在 I/O 操作中)时出现。所有标点符号变量在范围内都是全局的。它们提供了对什么是完全的显式控制大多数其他语言中的隐式行为:输出缓冲、输入行编号、输入和输出行结尾、数组索引等。

在没有首先本地化的情况下更改标点符号变量通常是一个严重的错误。未本地化的分配可能会改变系统中完全不相关部分中的代码行为,即使在您不是自己编写而只是在使用的模块中也是如此。

使用 local 是临时更改全局变量值的最简洁、最可靠的方法。它应始终在尽可能小的范围内应用,以尽量减少变量可能控制的任何“环境行为”的影响:

这里还有完整的perldoc perlvar文档 - 在网页中搜索单词“nasty_break”(我找不到直接的页内链接,但它靠近页面的开头)

在修改本文档中描述的大多数特殊变量的默认值时,您应该非常小心。在大多数情况下,您希望在更改这些变量之前对其进行本地化,因为如果您不这样做,更改可能会影响依赖于您已更改的特殊变量的默认值的其他模块。这是一次读取整个文件的正确方法之一:

  1. 打开我的 $fh, "<", "foo" 或者死 $!;
  2. 本地 $/; # 启用本地化 slurp 模式
  3. 我的 $ 内容 = ;
  4. 关闭 $fh;

但是下面的代码很糟糕:

  1. 打开我的 $fh, "<", "foo" 或者死 $!;
  2. 未定义 $/; # 开启 slurp 模式
  3. 我的 $ 内容 = ;
  4. 关闭 $fh;

由于其他一些模块,可能希望以默认的“行模式”从某个文件中读取数据,所以如果我们刚刚提供的代码已经执行,那么 $/ 的全局值现在会更改为在同一模块中运行的任何其他代码Perl 解释器。

通常,当变量被本地化时,您希望确保此更改影响可能的最短范围。因此,除非您已经在某个短的 {} 块内,否则您应该自己创建一个。例如:

  1. 我的 $content = '';
  2. 打开我的 $fh, "<", "foo" 或者死 $!;
  3. {
  4. 本地 $/;
  5. $内容 = ;
  6. }
  7. 关闭 $fh;

这是您自己的代码如何被破坏的示例:

  1. 对于(1..5){
  2. 讨厌的休息();
  3. 打印“$_”;
  4. }
  5. sub nasty_break {
  6. $_ = 5;
  7. # 用 $_ 做一些事情
  8. }

您可能希望此代码打印:

  1. 1 2 3 4 5

但是你得到:

  1. 5 5 5 5 5

为什么?因为 nasty_break() 修改 $_ 而不首先对其进行本地化。解决方法是添加 local():

  1. 本地 $_ = 5;
于 2010-10-29T18:08:14.647 回答
2
foreach (@rps_server_details) {
    @server_data = ();
    @server_data = split(/,/);
    @$esp_hosts = ();
    $filters{server_name} = $server_data[0];
    print "--->$_<--\n";
    {
        local *_;  # disconnects the remaining scope from the implicit 
                   # variables so you can clean up after the dirty api.
                   # NOTE: Submit a bug report against the offending module.
                   #       If you notice this across multiple api features
                   #       consider finding a different module for this task.
        $esp_hosts = $esp->get_hosts(fields => $fields, %filters) || die "$@";
    }
    print "--->$_<--\n";
于 2010-10-30T09:28:04.563 回答