如何构建基础模块

在处理大型Perl项目时,一个基础模块是一种为项目中的其他模块设置标准导入例程的好方法。使用基础模块,您可以配置记录器,启用pragmas并导入任何其他有用的例程。而不是输入

use warnings;
use strict;
use Data::Dumper 'Dumper';
use Log::Log4perl 'get_logger';
...

等等,您可以输入

use MyBase;

这节省了在每个模块顶部输入所有这些样板use语句的时间,并且它建立了一个一致的基础,使得所有模块都从相同的pragmas等开始运行。它还提供了一个中心位置来配置应用程序路径和其他全局编译时必需品。

构建您自己的基础模块

我将编写一个名为MyBase.pm的示例基础模块,以向您展示我是如何做的。我需要能够导出三个基本案例:pragmas、在MyBase命名空间中定义的符号和其他命名空间中的符号。在Perl中,符号通常是一个变量或子例程的引用。这是我的起始代码

package MyBase;

sub import {}

1;

在Perl中,import子例程非常重要:每当通过use导入模块时,都会调用它,因此这将是我导入基础模块中想要导入的pragmas和代码的触发器。

处理pragmas

查看Modern::Perl 源代码。该import子例程仅调用它想要导入的pragmas的import。聪明且简单!

package MyBase;
use v5.10.0;
use warnings;
use strict;

sub import {
  warnings->import;
  strict->import;
  feature->import(':5.10');
}

1;

现在,任何包含use MyBase;的模块都将获得警告、严格以及Perl 5.10的所有功能导入(例如saystate等等)。

处理外部符号

我所说的外部符号是指在其他模块中声明的子例程和变量,例如Data::Dumper::Dumper。这是一个总是很有用的子例程。

package MyBase;
use v5.10.0;
use warnings;
use strict;
use Data::Dumper;

sub import {
  warnings->import;
  strict->import;
  feature->import(':5.10');

  # get the importing package name
  my $caller = caller(0);

  do {
    no strict 'refs';
    *{"$caller\:\:Dumper"}  = *{"Data\:\:Dumper\:\:Dumper"};
  };
}

1;

在这里,我添加了use Data::Dumper;来导入模块。稍后在import()中,我将调用者的包名保存到$caller中,然后在do块中,我将Data::Dumper中的Dumper子例程复制到调用者的命名空间中。我在包引用中的分号前添加了转义字符,因为某些版本的Perl可能需要这样做,但我记不清楚是哪些了——现代Perl不需要。如果您发现符号表复制语法令人困惑,请参阅精通Perl的第7章和第8章,其中对它的工作方式有深入的解释。

处理局部符号

有许多类型的局部符号可能对导出有用:全局配置散列引用(可能一个用于开发,另一个用于生产),用于单例(如记录器和队列)等的访问器。我的应用程序使用Log::Log4perl,因此我将导出一个用于获取记录器的子例程

package MyBase;
use v5.10.0;
use warnings;
use strict;
use Data::Dumper;
use Log::Log4perl;

BEGIN {
  my $default_conf = q(
    log4perl.logger.Root           = DEBUG, Root
    log4perl.appender.Root         = Log::Log4perl::Appender::Screen
    log4perl.appender.Root.stderr  = 1
    log4perl.appender.Root.utf8    = 1
    log4perl.appender.Root.layout  = PatternLayout
    log4perl.appender.Root.layout.ConversionPattern = %C %m%n
  );
  Log::Log4perl->init(\$default_conf);
}

sub logger { Log::Log4perl->get_logger('Root') }

sub import {
  warnings->import;
  strict->import;
  feature->import(':5.10');

  # get the importing package name
  my $caller = caller(0);

  do {
    no strict 'refs';
    *{"$caller\:\:Dumper"}  = *{"Data\:\:Dumper\:\:Dumper"};
    *{"$caller\:\:logger"}  = *{"MyBase\:\:logger"};
  };
}

1;

我已导入Log::Log4perl模块,并在BEGIN块中初始化它(因此它在编译时发生)。我添加了一个新子例程logger,稍后在import子例程中将它复制到调用者的符号表中。现在,任何使用MyBase的模块都可以简单地调用logger来获取Log4perl对象。

在添加此类功能时要考虑的一件事是尽可能在import之外进行初始化。这是因为模块代码只加载一次,但每次使用MyBase时都会调用import。因此,请将import中的代码保持到最小所需——您不希望反复初始化Log4perl!

标量也很简单,以下是我可能导出项目版本的方法

${"$caller\:\:VERSION"}  = *{"MyBase\:\:VERSION"};

请注意,该行的第一个字符已从星号(类型全局)更改为美元符号(标量)。

启用堆栈跟踪

Perl的错误消息非常有助于调试,但我更倾向于查看堆栈跟踪以找出异常的原因。这可以通过使用来自Carp模块的confess子程序轻松地添加到一个基本模块中。

package MyBase;
use v5.10.0;
use warnings;
use strict;
use Data::Dumper;
use Log::Log4perl;
use Carp 'confess';

BEGIN {
  $SIG{'__DIE__'} = sub { confess(@_) };
  my $default_conf = q(
    log4perl.logger.Root           = DEBUG, Root
    log4perl.appender.Root         = Log::Log4perl::Appender::Screen
    log4perl.appender.Root.stderr  = 1
    log4perl.appender.Root.utf8    = 1
    log4perl.appender.Root.layout  = PatternLayout
    log4perl.appender.Root.layout.ConversionPattern = %C %m%n
  );
  Log::Log4perl->init(\$default_conf);
}

sub logger { Log::Log4perl->get_logger('Root') }

sub import {
  warnings->import;
  strict->import;
  feature->import(':5.10');

  # get the importing package name
  my $caller = caller(0);

  do {
    no strict 'refs';
    *{"$caller\:\:Dumper"}  = *{"Data\:\:Dumper\:\:Dumper"};
    *{"$caller\:\:logger"}  = *{"MyBase\:\:logger"};
  };
}

1;

我在代码中添加了一条导入Carp模块的语句,并在BEGIN块中安装了一个针对伪信号__DIE__的信号处理程序。这将在应用程序抛出异常时被调用。处理程序是一个匿名子程序,它将对异常调用confess。这将打印堆栈跟踪并退出。

考虑使用Import::Base

我不确定我的代码是否处理了所有边界情况。它满足了我的需求,但如果您正在共享项目代码,请考虑使用Import::Base,它可以帮助您完成所有这些操作。以下是使用Import::Base重写的我的基本模块的样子:

package MyBase;
use base 'Import::Base';
use v5.10.0;
use warnings;
use strict;
use Data::Dumper;
use Log::Log4perl;
use Carp 'confess';

BEGIN {
  $SIG{'__DIE__'} = sub { confess(@_) };
  my $default_conf = q(
    log4perl.logger.Root           = DEBUG, Root
    log4perl.appender.Root         = Log::Log4perl::Appender::Screen
    log4perl.appender.Root.stderr  = 1
    log4perl.appender.Root.utf8    = 1
    log4perl.appender.Root.layout  = PatternLayout
    log4perl.appender.Root.layout.ConversionPattern = %C %m%n
  );
  Log::Log4perl->init(\$default_conf);
}

sub logger { Log::Log4perl->get_logger('Root') }

our @IMPORT_MODULES = (
  'warnings',
  'strict',
  'feature' => [':5.10'],
  'Data::Dumper' => ['Dumper'],
  'MyBase',
);

our @EXPORT = ( 'logger' );

1;


这篇文章最初发表在PerlTricks.com上。

标签

David Farrell

David是一位专业程序员,他经常在Twitter博客上分享代码和编程艺术。

浏览他们的文章

反馈

这篇文章有问题吗?请通过在GitHub上打开问题或拉取请求来帮助我们。