perl - 向perl应用程序添加动态代码的最佳方法

  显示原文与译文双语对照的内容

我知道这里问题的特定实例以前已经被回答过:

在Perl僧侣中也有不错的答案:

但是,我想要一种将functionallity添加到一个将是以下的Perl应用程序的鲁棒方法:

  • 如果不需要代码,则不需要编译。
  • 易于调试的: 在动态代码出错时报告错误,应在动态代码处指向正确的位置。
  • 易于扩展的: 添加新代码应该像添加新文件或者directory+file一样简单。
  • 易于调用的: 主应用程序应该能够使用"添加",但没有什么麻烦。 一种有效的机制来检查"添加"是否已经加载,如果没有加载,将是一个加入。

下面是一些示例,这些示例将从一个很好的解决方案中获益:

  • 从不同应用程序中移动数据的一组脚本。 例如从OpenCart移动数据到 Prestashop,在数据模型中每个非特定实体的实体都有处理输入或者输出的特定的"添加",然后中间数据模型负责处理数据的转换。 这可以用来移动任何 direction 中的数据,甚至可以在同一电子商务的不同版本之间移动数据。

  • 需要在不同位置呈现不同类型的HTML的web应用程序。 每个"模块"都知道如何处理特定信息并接受参数来进行操作。 模块输出 HTML 。文档列表。另一个文档。另一个标题等等。

下面是一些我曾经使用过的例子。

在运行时加载函数并输出可能的编译错误:


eval `cat $file_with_function`;


if( $@ ) {


 print STDERR $@,"n";


 die"Errors at file $file_with_functionn";


}



使用 File::Slurp 更可靠:


eval read_file("$file_with_function", binmode => ':utf8');



检查某个函数是否已经定义:


if(!defined &myfunction ) {


 die"myfunction is not definedn";


}



函数可以从下面的。 这对于一个函数很好,但对于许多函数却没有。

如果函数被放置在模块中:


require $file_with_function; # needs the".pm" extension, i.e. addon/func.pm


$name_of_module->import(); # need to know the module name, i.e. Addon::Func



$name_of_module->myfunction(...);



require 可以是 protected inside,然后是 eval,然后像以前一样使用 $@

使用 Module::Load:


load $name_of_module;



后跟 import 并以同样的方式使用。 安全 concern,因为它可以假定动态代码来自受信任的位置。 有更好的方法哪种方法被认为是的实践

如果有帮助的话,我将在舞者的框架中使用这个解决方案。

收费 : 给出了评论,我增加了一些信息。 所有我想到的情况都有共同之处:

  • 有一个上的动态代码。 可能还有很多。
  • 每位代码都具有相同的接口 。
时间: 原作者:

鉴于评论和缺乏回应,我已经做了一些研究来回答我自己的问题。 欢迎评论或者其他答案 !

动态代码

根据动态代码,我指的是在运行时计算的代码。 一般来说,我认为最好编译一个应用程序,以便在开始执行之前提供Perl编译器的所有错误检查。 添加到 use strictuse warnings,你可以捕获许多常见错误。 那么为什么要使用动态代码? 以下是我考虑的原因:

  • 应用程序执行许多不同的操作,根据执行上下文进行选择。 例如应用程序从文件中提取某些属性。 提取它们的方法取决于文件类型,我们想要处理许多文件类型,但我们不希望更改。 我们还希望应用程序快速启动。
  • 应用程序需要以一种不需要重新启动应用程序的方式以一种不需要应用程序重新启动的方式展开。
  • 我们有一个包含许多功能的大型应用程序。 当我们部署应用程序时,我们不想提供所有可以能的特性,也许是因为我们不能在所有平台上运行。 通过只抛出具有所需功能的文件,我们有一个不需要更改任何代码或者插件配置文件的分发 。
  • 我们该怎么做?

考虑到Perl提供的可能性,添加动态代码的解决方案有两种: 使用 eval 和使用 require 。 这样就有了模块可以帮助你以更容易或者更易于维护的方式做事。

快速而肮脏的方式

在的运行时,eval 使用表单 eval EXPR 编译一块Perl代码。 表达式可以是字符串,但我建议将代码放入一个文件中,并将其他类似文件分组到一个方便的地方。 然后,如果可能使用 File::Slurp:


eval read_file("$file_with_code", binmode => ':utf8');


if( $@ ) {


 die"$file_with_code: error $@n";


}


if(!defined &myfunction ) {


 die"myfunction is not defined at $file_with_coden";


}



指定 read_file的字符集将确保正确地解释该文件。 检查编译是否正确以及是否定义了预期的函数也是很好的。 在 $file_with_code 中,我们将拥有:


sub myfunction(...) {


 # Do whatever; maybe return something


}



然后你可以正常调用函数。 函数将是不同的,具体取决于加载的文件。 简单和动态。

模块化方式( 推荐)

我想用可维护性来做它的方法是使用 requireuse 不同,在编译时计算,require 可以用于在运行时加载一个 MODULE 运行时。 在调用 require的各种方法中,我将执行以下操作:


my $mymodule = 'MyCompany::MyModule'; # The module name ends up in $mymodule


require $mymodule;



use 不同,require 将加载 MODULE,但不会执行 import 。 所以我们可以使用 MODULE 中的任何函数,这些函数名不会对调用命名空间进行 polute 。 要访问该函数,我们需要使用:


$mymodule->myfunction($a, $b);



关于参数如何传递的信息见下面。 这种调用函数的方法将在 $a$b 之前添加一个参数,该参数通常为 $self

由于 require 将尝试加载 MODULE,并且 MODULE 可以能不存在或者它不能编译,因这里最好使用:


eval"require $mymodule";



然后 $@ 可以用于检查loading+compiling进程中的错误。 我们还可以检查函数是否已经定义为:


if( $mymodule->can('myfunction') ) {


 die"myfunction is not defined at module $mymodulen";


}



在这种情况下,我们需要为每个模块创建一个目录,并为每个模块创建一个 .pm 扩展名:


MyCompany


 MyModule.pm



MyModule.pm 内部,我们将拥有:


package MyCompany::MyModule;



sub myfunction {


 my ($self, $a, $b);



 # Do whatever; maybe return something


 # $self will be 'MyCompany::MyModule'


}



1;



package 位非常重要,并且将确保我们放入的任何定义都将位于 MyCompany::MyModule 命名空间。 最后的1; 将告诉 require MODULE 初始化是正确的。

如果希望使用其他可以支持调用方命名空间的库实现 MODULE,我们可以使用 namespace::clean MODULE 。 这个 MODULE 将确保调用方不会得到来自我们正在定义的MODULE的名称空间的任何增加。 它是以这种方式使用的:


package MyCompany::MyModule;



# Definitions by these modules will not be available to the code doing the require


use Library1 qw(def1 def2);


use Library2 qw(def3 def4);


...



# Private functions go here and will not be visible from the code doing the require


sub private_function1 {


. . .


}


...



use namespace::clean;



# myfunction will be available


sub myfunction {


 # Do whatever; maybe return something


}


...



1;



如果我们包含一次 MODULE,会发生什么?

简短的回答是:没有什么。 Perl跟踪哪些模块已经加载,以及使用 %INC 变量的位置。 userequire 都不会加载两次库。 use 将所有导出的名称添加到调用方的命名空间。 require 也不会这么做的。 如果你想检查已经加载了 MODULE,你可以使用 %INC 或者更好的,你可以使用 module::loaded: 中的核心。


use Module::Loaded;



if(!is_loaded( $mymodule ) {


 eval"require $mymodule" );


. . .


}



如何确保Perl找到我的MODULE 文件?

对于 userequire Perl使用 @INC 变量定义目录列表,这些目录将用于查找库。 通过将新目录添加到 PERL5LIB 环境变量或者通过使用它,可以将新目录添加到( 其中之一) 中:


use lib '/the/path/to/my/libs';



helper 库

我发现一些库可以用来使使用动态机制的代码更加易于维护。 它们是:

  • 如果是 MODULE: 将加载 MODULE 或者不依赖于条件: use if CONDITION, MODULE => ARGUMENTS; 也可以用于卸载模块。
  • 在试图加载并可能用于检查 MODULE 版本或者它的依赖项时,模块:: load:: 条件插件不会出现在你的身上。 它还可以一次加载一个模块列表,甚至在执行之前检查它们的版本。

从模块:: load:: 条件文档中获取:


use Module::Load::Conditional qw(can_load);



my $use_list = {


 CPANPLUS => 0.05,


 LWP => 5.60,


 'Test::More' => undef,


};



print can_load( modules => $use_list )


? 'all modules loaded successfully'


 : 'failed to load required modules';



原作者:
...