【问题标题】:best practices when using Perl modules使用 Perl 模块时的最佳实践
【发布时间】:2017-10-23 02:05:02
【问题描述】:

我基本上是模块的新手,我正在尝试在我的脚本中使用它们。 我很难找到正确使用它们的正确方法,我希望得到您的建议。

让我快速解释一下我要做什么:

我的脚本正在根据 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 而且,这是否意味着我必须在每个模块中都有相同的子程序? 示例:子执行

因为我想向模块发送不同的数据......

任何提示? 再次感谢 问候,

【问题讨论】:

  • 模块是面向对象的吗?它中是否总是有相同的功能,例如doexecuterun 或类似的东西?能否请edit 提供一个示例模块?
  • 这是一个很好的问题,这就是我最后要问的问题,我应该创建 OO 模块还是常规模块,我应该在每个模块中使用相同类型的子例程? (否则很难自动调用模块,因为每个模块都在做不同的事情)。我可以编辑我的帖子并发布一个模块,但它不是一个动作模块
  • 定义接口很重要。我要写一个答案,但可能需要一段时间,因为它可能很长。
  • 啊,这很有趣,再次感谢您的帮助。我想我可以像你说的那样创建一个带有 execute() 子例程的 OO 模块,但是我必须将所有参数传递给它,或者找到一种从模块和每个模块中读取 XML 的方法

标签: xml perl module libxml2 xml-libxml


【解决方案1】:

首先,您需要提供一个清晰的界面。每个模块都需要具有相同的结构。不管是不是OOP,都需要暴露同一个接口。

这是FFTG::Rename 的非面向对象实现示例。我遗漏了很多东西,但我认为发生了什么很清楚。

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 keyword 很方便,因为它也可以用来检查函数是否存在。

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 方法。

您可以通过使用-&gt; 对象表示法来调用类方法,使其保持非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);

这显然更清晰,更可取。

【讨论】:

  • 再次感谢您的清晰解释,这很有意义!今天下午我会做一些测试,然后回答你(并将问题标记为已解决)!再次感谢您花时间写这篇文章
  • 在我回答之前只是一个简单的问题,你写了我的 ($args) = @_;在子例程中,但是是否有将参数(动作内容)发送到子程序的最佳方法?我想我必须将它们全部放在哈希中,然后将它们发送到 run(%hash) ?
  • 我的错,我刚刚注意到 $parsed_node_information,我想我必须用操作信息填充这个变量
  • @olivierg 是的。我想既然您已经深入研究了整个 XML 内容,您将知道如何使用 LibXML 来做到这一点。我也必须查一下,这对概念并不重要,所以我不这样做。我认为一个带有键/值对的简单 hashref 应该可以工作。我认为将其抽象化,这样您就不会从 XML 解析器中传递实际的 $node,因为这样您还可以将传递格式从 XML 更改为例如YAML 或纯文本或任何你想要的,但你的模块保持不变。
  • 我明白了,再次感谢(事实上这不是我的问题的目标,如果我做不到,也许我会为此创建第二个主题,因为我'我也是 libxml 的新手,我现在正在测试您的解决方案(暂时不使用 args),如果一切正常,我会将其标记为已解决,再次感谢您的帮助!
猜你喜欢
  • 2011-12-06
  • 2018-05-10
  • 2023-03-20
  • 2016-10-05
  • 1970-01-01
  • 1970-01-01
  • 2011-05-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多