【问题标题】:Perl module loading - Safeguarding against: perhaps you forgot to load "Bla"?Perl 模块加载 - 防范:也许您忘记加载“Bla”?
【发布时间】:2011-11-07 00:01:20
【问题描述】:

当你运行perl -e "Bla->new" 时,你会得到这个众所周知的错误:

Can't locate object method "new" via package "Bla"
(perhaps you forgot to load "Bla"?)

由于我的疏忽,前几天发生在 Perl 服务器进程中。有多个脚本,其中大多数都有正确的use 语句。但是有一个脚本在第 123 行的sub blub 中执行Bla->new,但在顶部缺少use Bla,并且当它被点击而没有使用Bla 的任何其他脚本被加载时之前的服务器进程,然后砰!

单独测试脚本是防止这种特殊错误的明显方法,但遗憾的是,代码依赖于庞大的环境。您知道防止这种疏忽的另一种方法吗?

更新

这是一个例子,PPI(尽管有其优点)在其对 Perl 的看法中受到限制:

use strict;
use HTTP::Request::Common;

my $req = GET 'http://www.example.com';
$req->headers->push_header( Bla => time );

my $au=Auweia->new;

__END__
PPI::Token::Symbol          '$req'
PPI::Token::Operator        '->'
PPI::Token::Word            'headers'
PPI::Token::Operator        '->'
PPI::Token::Word            'push_header'

PPI::Token::Symbol          '$au'
PPI::Token::Operator        '='
PPI::Token::Word            'Auweia'
PPI::Token::Operator        '->'
PPI::Token::Word            'new'

设置标题和分配Auweia->new 解析相同。所以我不知道你怎么能建立在这样一个摇摇欲坠的基础上。我认为问题在于Auweia 也可能是一个子程序; perl.exe 直到运行时才知道。

进一步更新

好的,从下面 @Schwern 的指导性 cmets 中我了解到 PPI 只是一个标记器,如果您接受它的限制,您可以在它的基础上进行构建。

【问题讨论】:

  • 我想知道 perlcritic 是否有对此有所帮助的政策。
  • @Lumi PPI 标记正确。它并不不稳定,但它只是一个标记器。你必须提供意义。我会更新我的答案来处理这种情况。

标签: perl qa perl-module


【解决方案1】:

测试是唯一值得努力的答案。如果代码包含忘记加载类等错误,则可能包含其他错误。无论遇到什么障碍,都要让它可测试。否则你就是在补筛子。

也就是说,您有两个选择。您可以使用Class::Autouse,如果它尚未加载,它将尝试加载模块。它很方便,但因为它会影响整个过程,所以可能会产生意想不到的影响。

或者您可以使用PPI 扫描您的代码并查找所有类方法调用。 PPI::Dumper 非常便于理解 PPI 如何看待 Perl。

use strict;
use warnings;

use PPI;
use PPI::Dumper;

my $file = shift;
my $doc = PPI::Document->new($file);

# How PPI sees a class method call.
#    PPI::Token::Word      'Class'
#    PPI::Token::Operator  '->'
#    PPI::Token::Word      'method'
$doc->find( sub {
    my($node, $class) = @_;

    # First we want a word
    return 0 unless $class->isa("PPI::Token::Word");

    # It's not a class, it's actually a method call.
    return 0 if $class->method_call;

    my $class_name = $class->literal;

    # Next to it is a -> operator
    my $op = $class->snext_sibling or return 0;
    return 0 unless $op->isa("PPI::Token::Operator") and $op->content eq '->';

    # And then another word which PPI identifies as a method call.
    my $method = $op->snext_sibling or return 0;
    return 0 unless $method->isa("PPI::Token::Word") and $method->method_call;

    my $method_name = $method->literal;

    printf "$class->$method_name seen at %s line %d.\n", $file, $class->line_number;
});

【讨论】:

  • 谢谢。 PPI 对 Perl 的看法似乎很有限。 (将更新我的问题以说明如何。)是的,我绝对同意,测试是要走的路;唉,代码的复杂性,如何组织它的规则使测试变得困难;他们实际上阻止了它被正确测试。还要看看 Class::Autouse。
  • @Lumi 我已经更新它来防御$obj->method1->method2。不幸的是,如果不运行代码,我们无法判断Thing->methodThing()->method 还是Thing::->method,这是我们所知道的。您可以添加检查以查看是否安装了名为 Thing 的模块。出于您的目的,如果您的 linter 找到一个实际上是子例程调用的实例,请修改代码以执行 Thing()->method 以解决歧义。 Perl 充满了这些歧义,这就是为什么 PPI 只是一个标记器以及为什么静态分析只能走这么远的原因。
【解决方案2】:

你没有说你在什么服务器环境下运行,但从你所说的听起来你可以在执行任何单独的页面之前提前预加载所有模块。这不仅可以防止您描述的问题(每个脚本都必须记住加载它使用的所有模块),而且还可以节省您的内存。

在预分叉服务器中(通常与 mod_perl 和 Apache 一起使用),您确实希望在服务器第一次分叉之前加载尽可能多的代码,以便代码存储一次在写时复制共享内存中,而不是在每个子进程按需加载时多次。

有关在 Apache 中预加载的信息,请参阅Practical mod_perl 部分

【讨论】:

  • 谢谢,马克。这是一个自定义服务器架构,具有自定义缺点。因此,大多数自定义问题。
猜你喜欢
  • 2016-09-27
  • 1970-01-01
  • 2017-09-26
  • 2016-07-21
  • 1970-01-01
  • 1970-01-01
  • 2023-01-27
  • 2017-06-29
  • 2016-11-27
相关资源
最近更新 更多