[英]best practices when using Perl modules
我基本上是模块的新手,我正在尝试在脚本中使用它们。 我在寻找正确使用它们的正确方法时遇到了麻烦,我想就此提出您的建议。
让我快速解释一下我要做什么:
我的脚本基于XML文件中的数据进行了一些文件传输。
所以基本上,我有具有类似内容的XML文件:
<fftg>
<actions>
<!-- Rename file(s) -->
<rename>
<mandatory>0</mandatory>
<file name="foo" to="bar" />
</rename>
<!-- Transfer file(s) -->
<transfer>
<mandatory>0</mandatory>
<protocol>SFTP</protocol>
<server>fqdn</server>
<port>22</port>
<file name="bar" remotefolder="toto" />
</transfer>
<!-- Transfer file(s) -->
<transfer>
<mandatory>0</mandatory>
<protocol>SFTP</protocol>
<server>fqdn</server>
<port>22</port>
<file name="blabla" remotefolder="xxxx" />
<file name="blabla2" remotefolder="xxxx" />
</transfer>
</actions>
</fftg>
简而言之,我有一个执行“动作”的脚本。 每个动作可以重复X次。
现在,而不是使用带有一堆子例程的重要脚本等。我认为最好为我的应用程序创建模块,并将操作放入模块中。
例如 :
FFTG::Rename
FFTG::Transfer
FFTG::Transfer::SFTP
FFTG::Transfer::FTP
等等(我创建了所有这些模块,它们可以独立正常工作)
并根据XML文件中指定的操作调用这些模块。 人们可以根据需要创建新的模块/动作(我希望将其模块化)。
现在,我不知道如何正确地做到这一点。
所以我的问题是:请问这样做的最好方法是什么?
当前,我的脚本正在读取这些操作,例如:
# Load XML file
my $parser = XML::LibXML->new();
my $doc = $parser->parse_file($FFTG_TSF . "/" . $tid . ".xml");
# Browse XML file
foreach my $transfer ($doc->findnodes('/fftg')) {
# Grab generic information
my($env) = $transfer->findnodes('./environment');
my($desc) = $transfer->findnodes('./description');
my($user) = $transfer->findnodes('./user');
print $env->to_literal, "\n";
# Browse Actions
foreach my $action ($doc->findnodes('/fftg/actions/*')) {
my $actiontype = ucfirst($action->nodeName());
# how do i select a module from the $actiontype here ? ($actiontype = Rename or Transfer)
# i can't do : use FFTG::$actiontype::execaction(); or something for example, it doesnt work
# and is it the right way of doing it ?
}
}
但这也许不是正确的思考方式。 (我正在使用Lib :: LibXML)如何“动态”调用模块(使用名称中的变量,例如FFTG :: $ actiontype,这是否也意味着我必须具有相同的子例程在每个模块中?示例:子执行
因为我想向模块发送不同的数据...
有什么提示吗? 再次感谢您,
首先,您需要提出一个清晰的界面。 每个模块都必须具有相同的结构。 是否为OOP无关紧要,但是它们都需要公开相同的接口。
这是FFTG::Rename
非OOP实现示例。 我遗漏了很多东西,但是我认为这很明显。
package FFTG::Rename;
use strict;
use warnings;
sub run {
my ($args) = @_;
if ($args->{mandatory}) {
# do stuff here
}
# checks args...
# do sanity checks...
return unless -f $args->{file}->{name}; # or whatever...
rename $args->{file}->{name}, $args->{file}->{to} or die $!;
return; # maybe return someting meaningful?
}
现在,假设我们有一堆。 我们如何加载它们? 有几种方法可以做到这一点。 我已经省略了将参数放入run
函数的部分。 您需要从XML中获取内容,并以与所有这些功能相同的方式传递它,但是我认为这与问题无关。
最明显的是将它们全部手动加载到脚本中。
#!/usr/bin/env perl
use strict;
use warnings;
use XML::LibXML;
# load FFTG modules
use FFTG::Rename;
# ...
加载它们后,您可以调用该函数。 exist
关键字很方便,因为它也可以用于检查功能是否存在。
foreach my $action ( $doc->findnodes('/fftg/actions/*') ) {
my $actiontype = ucfirst( $action->nodeName );
no strict 'refs';
if ( exists &{"FFTG::${actiontype}::run"} ) {
&{"FFTG::${actiontype}::run"}->( $parsed_node_information );
} else {
# this module was not loaded
}
}
不幸的是,非OO方法no strict 'refs'
,这不是很漂亮。 最好以面向对象的方式进行操作。 但我会坚持使用这个答案。
这种方式的明显缺点是,您需要一直加载所有模块,并且每当创建一个新模块时,都需要添加该模块。 这是最简单的方法,但是维护也最高。
另一种方法是使用自动加载和查找表,该表定义了允许的操作。 如果您希望程序只按需加载模块,因为您知道在每次调用时都不需要所有模块,但是您还希望控制要加载的模块,这很有意义。
除了加载所有这些,还可以将加载外包给Module :: Runtime 。
use Module::Runtime 'require_module';
use Try::Tiny;
my %modules = (
'rename' => 'FFTG::Rename',
# ...
);
foreach my $action ( $doc->findnodes('/fftg/actions/*') ) {
try {
no strict 'refs';
require_module $modules{$action};
&{"FFTG::${actiontype}::run"}->($parsed_node_information);
}
catch {
# something went wrong
# maybe the module does not exist or it's not listed in the lookup table
warn $_;
};
}
我还添加了Try :: Tiny来照顾错误处理。 它使您可以控制出现问题时的处理方式。
这种方法使您可以控制允许执行哪些操作,如果您偏执,那很好。 但是它仍然需要您维护脚本并将新模块添加到%modules
查找表中。
第三种最通用的方法是使用Module :: Runtime来动态加载内容,而无需查找表。
use Module::Runtime 'require_module';
use Try::Tiny;
foreach my $action ( $doc->findnodes('/fftg/actions/*') ) {
try {
my $actiontype = ucfirst($action->nodeName);
require_module "FFTG::${actiontype}";
no strict 'refs';
&{"FFTG::${actiontype}::run"}->($parsed_node_information);
}
catch {
# something went wrong
# the module does not exist
};
}
这样维护最少,但是危险性更高。 您不知道要输入什么数据,现在没有健全性检查。 我想不出一种方法来利用这一点,但是可能有一种方法。 不过,现在不需要编辑脚本并保持模块列表为最新。
我可能会选择第二种方法。 它使您可以控制,并且仍然使内容保持动态。 我不会采用我曾经使用过的非OOP方法。
您可以使它保持非OOP状态,并且仍然可以通过使用->
对象符号来调用类方法来摆脱no strict 'refs'
。 然后您的包裹将如下所示。
package FFTG::Rename;
use strict;
use warnings;
sub run {
my (undef, $args) = @_;
# ...
}
undef
是不捕获$class
(不是$self
),因为我们不需要它。 或者也许我们这样做,以便进行日志记录。 这取决于。 但是,有了这个,您基本上可以按以下方式为查找表解决方案调用类方法。
require_module $modules{$action};
$modules{$action}->run($parsed_node_information);
这显然是更清晰和更可取的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.