如何检查是否已导入所有的Perl模块?

3

假设我有一个Perl模块:

use Foo::Bar;

sub add {
    return Foo::Bar::func() + Foo::Buzz::func();
}

这个模块存在一个错误,因为它忘记了使用Foo::Buzz。我的单元测试不一定会捕获到这个错误,例如如果Foo::Buzz的测试运行得更早并在add()运行之前导入了Foo::Buzz。如果我在生产代码中使用这个模块,它将因为未导入Foo::Buzz而失败。

我如何检查我在代码中使用的所有模块是否都已经被导入?

编辑:我希望在部署代码之前检查代码,以避免出现任何错误。该示例将在生产环境中失败,我希望在此之前就能捕获到这个错误,例如当我运行我的单元测试时。我想要一个工具或一些代码,在部署之前运行它可以捕获这个错误,就像Python中的flake8一样。


与Java不同,Perl是一种动态语言。错误会在运行时发生。这就是动态语言的本质。 - ceving
你想要做什么?你可以通过测试 *{'Foo::Buzz::'}{HASH} 来检查是否已加载 Foo::Buzz,这是 Foo::Buzz 的存储区。但是,除非你满意于一种简单的方法,只需在代码中查找所有 Xxx::Yyy 等内容,否则枚举使用的包远非简单。 - Borodin
这个例子在生产环境中会失败,我想在那之前捕获错误,例如当我运行单元测试时。我想要一个工具或一些代码,在部署之前可以捕获这个错误,就像 Python 的 flake8 一样。 - Sjoerd
2个回答

2

简短的回答是不行。由于Perl是一种动态语言,您无法在运行时检查是否加载了所有模块,就像您无法检查代码中是否存在其他错误一样。

您仍然可以使用一些静态代码分析,尝试在文件中查找This::Pattern,其中未呈现use This::Pattern;,但这并不能保证任何事情。


0

如果Perl是严格的动态语言,您可以轻松地检查程序中是否安装了模块。问题在于Perl并不是100%动态的。它会进行一些编译工作,并且在检查模块之后完成部分编译工作。

Bulrush的想法是正确的。不幸的是,您无法使用use子句来实现此目的。 use在预编译时进行检查,因此您将在eval执行之前收到错误。

然而,在use perldoc页面中有一个提示:

  • use Module LIST
  • use Module
  • use VERSION

    从命名模块中向当前包导入一些语义,通常通过将某些子例程或变量名称别名到您的包中来实现。它与以下内容完全等效

    BEGIN { require Module; Module->import( LIST ); }

好了!你可以在BEGIN子句中使用require,该子句甚至在文件的其余部分被解析之前就已执行。你也可以在那里使用eval来查看是否有效。由于作用域问题,你需要使用一个变量作为标志来查看是否有效。普通的词法作用域变量将在离开BEGIN子句时消失。

BEGIN {
    our $FOO_BAR_available = 0;        # Must be a package variable
    eval {
        require Foo::Bar;
        Module->import( qw(...) );     # No need if you don't import any subroutines
    };
    if (not $@ ) {
        $FOO_BAR_AVAILABLE = 0;
    }
}

然后在你的程序中,你会有:

our $FOO_BAR_available;
if ( not $FOO_BAR_available ) {
    # Here be dragons...
}
else {
    # Back to your normal code...
}

our $FOO_BAR_available 有点令人困惑。你并没有重新声明这个变量,只是声明你想要使用这个变量而不用前缀全包名。该变量在 BEGIN 子句中设置,这不会影响其值。

如果该模块被编写得正确,则可以完全跳过使用包变量。模块应该设置一个名为 $VERSION 的包变量。您可以将此变量用作标志:

BEGIN {
    eval {
        require Foo::Bar;
        Module->import( qw(...) );   # No need if you don't import any subroutines
    };
}

请注意,我不仅不必声明包变量,甚至不必验证eval语句是否成功。

然后在您的程序中...

if ( not $FOO::BAR::VERSION )  {
    # Here be dragons...
}
else {
    # Back to your normal code...
}

如果模块设置了$VERSION变量,那么你就知道它已被加载。否则,你就知道该模块未被加载。


附录

我想在将代码部署到生产环境之前检查它,以避免出现任何错误。这个例子在生产环境中会失败,我希望在那之前捕获错误,例如当我运行单元测试时。

以下是我的建议。虽然不像运行脚本那么简单,但效果要好得多:

  • 首先,定义您的生产环境:它使用哪个版本的Perl?使用了哪些模块?这将帮助开发人员知道可以期望什么。我知道Foo::Bar很好,但是我不应该使用Far::Bu,因为生产环境没有那个模块。这是第一步。我惊讶于有多少地方不知道他们的生产环境上有什么。
  • 使用Vagrant。这定义了一个与您的生产环境匹配的虚拟机。开发人员可以将其下载到自己的系统中,并在桌面上拥有生产环境的副本。
  • 使用Jenkins。Jenkins是一个持续构建引擎。是的,您不需要编译Perl,但您仍然可以从Jenkins中受益:

    • Jenkins可以为您运行单元测试。每次更改代码时都进行自动测试。您可以尽早捕获错误。
    • 您的Jenkins系统可以与您的生产机器匹配-相同的Perl版本,相同的Perl模块。如果它在Jenkins构建机器上无法运行,因为某些东西未安装,则很有可能它也无法在生产环境中运行。
    • 您可以通过Jenkins进行安装。 Jenkins可以打包您的发布版本,您可以使用它来安装已知版本。不要从开发人员的系统中拉取代码,然后发现系统上有一些不在您的版本控制系统中的东西。我不知道有多少次我看到开发人员从他们的机器上启动了某些东西用于生产(经过充分测试!相信我!),然后我们发现我们的版本控制系统中没有那个代码,因为开发人员忘记了检查某些内容。

通常情况下,您不会在生产环境中运行flake8。那时,为时已晚。

Perl有很多好用的工具可以执行类似的功能:

  • Perlbrew:这允许您的开发人员在其开发系统中安装一个单独的Perl程序和它自己的CPAN模块库。他们可以使用这个来匹配Perl版本和所需的模块到生产环境。这样,他们就是按照相同的规则进行操作。
  • Perlcritic:这检查您的模块是否符合Damian Conway在他的Perl最佳实践中制定的编码标准。
  • B::Lint:这就像C语言中旧的lint程序,可以捕获编码问题。

然而,这些都是在您准备好在生产环境中运行之前要做的事情。使用Vagrant帮助开发人员设置自己的私有生产环境进行测试。使用Jenkins确保您在非常类似于生产环境的环境中进行测试,并尽早捕获错误,而不是在UAT测试之后。


非常感谢您详细的回答,但是似乎您误解了我的问题。我想在部署代码到生产环境之前检查它,以避免出现任何错误。如果我的问题没有表述清楚,我很抱歉。 - Sjoerd
为什么这个答案被踩了?它技术上有问题吗? - octopusgrabbus
你编写了一个Perl脚本,想要验证你在脚本中包含的模块是否在生产环境中?这可以是一个shell脚本吗?你可以查找use语句,然后通过perldoc运行它们。如果perldoc返回退出代码为零,则表示存在。如果不是,则不存在。这样可以吗?如果可以,我会更新我的答案。 - David W.
@Sjoerd,希望你明白,在系统_B_上是无法查看系统_A_是否安装了Perl模块的。你可以在Perl脚本中使用这种技巧来验证模块是否已安装,以及如果未安装该怎么办。例如,我可能会在一个邮件模块上使用,如果没有安装则退而使用Net::SMTP。你甚至可以使用CPAN来加载这些模块,然后运行你的程序。(这将是一个不错的技巧)。 use的整个目的就是在运行程序之前验证模块是否存在。 - David W.
@Sjoerd 请看我的附言。 - David W.
我给它点了踩。从技术上讲,这个答案没有任何问题。它是对一个完全不同问题的很好的回答。它似乎甚至没有部分地回答在这里所问的问题。 - tobyink

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接