我对 Perl 还很陌生,想知道 Perl 的子例程的最佳实践是什么。子程序可以太大吗?
我现在正在编写一个脚本,它可能需要调用另一个脚本。我是否应该将旧脚本以子程序的形式集成到新脚本中?我需要将一个参数传递给脚本并需要一个返回值。
我猜我必须做某种黑魔法才能从原始脚本中获取输出,所以子程序化它是有道理的,对吧?
我对 Perl 还很陌生,想知道 Perl 的子例程的最佳实践是什么。子程序可以太大吗?
我现在正在编写一个脚本,它可能需要调用另一个脚本。我是否应该将旧脚本以子程序的形式集成到新脚本中?我需要将一个参数传递给脚本并需要一个返回值。
我猜我必须做某种黑魔法才能从原始脚本中获取输出,所以子程序化它是有道理的,对吧?
在编写代码时避免“黑魔法”总是一个好主意。您永远都不想跳槽并想出一个不直观的技巧来解决问题,尤其是在以后需要支持该代码时。诚然,它发生了,我们都为此感到内疚。情况可能会严重影响“让该死的事情发挥作用”。
关键是,最佳实践始终是使代码清晰易懂。请记住,根据我的经验,对于 Perl 代码尤其如此,您在几个月前自己编写的任何代码都可能是由其他人编写的。因此,即使您是唯一需要支持它的人,也请帮自己一个忙,让它易于阅读。
不要执着于宽泛的想法,例如“喜欢更多的文件而不是更大的文件”或“喜欢更小的方法/子例程而不是更大的文件”等。这些确实是很好的指导方针,但要应用指导方针的精神而不是信条它。保持代码干净、易于理解和可维护。如果这意味着偶尔的大文件或大方法/子例程,那就这样吧。只要有意义。
一个关键的设计目标是关注点分离。理想情况下,每个子程序都执行一个明确定义的任务。从这个角度来看,主要问题不是围绕子程序的大小而是围绕它的焦点。如果您的程序需要多个任务,则意味着多个子例程。
在更复杂的场景中,您最终可能会得到逻辑上属于一起的子例程组。它们可以组织成库,甚至更好的模块。如果可能,您希望避免最终出现需要相互通信的多个脚本的情况,因为一个脚本将数据返回到另一个脚本的通常机制是乏味的:第一个脚本写入标准输出,第二个脚本写入标准输出。脚本必须解析该输出。
几年前,我开始从事一项需要构建大量命令行脚本的工作(至少结果是这样;一开始,我们并不清楚我们在构建什么)。当时我还比较缺乏经验,也没有很好地组织代码。事后看来,我应该在编写模块而不是脚本的前提下工作。换句话说,真正的工作将由模块完成,脚本(用户在命令行上执行的代码)将仍然是非常小的前端,可以以各种方式调用模块。这将促进代码重用和所有这些好东西。生活和学习,对吧?
另一个尚未提及的在脚本中重用代码的选项是将通用代码放入模块中。如果您将共享子例程放入一个或多个模块中,您可以使您的脚本简短并专注于它们所做的特殊事情,同时以易于访问和重用的形式隔离公共代码。
例如,这是一个带有几个子例程的模块。把它放在一个名为的文件中MyModule.pm
:
package MyModule;
# Always do this:
use strict;
use warnings;
use IO::Handle; # For OOP filehandle stuff.
use Exporter qw(import); # This lets us export subroutines to other scripts.
# These may be exported.
our @EXPORT_OK = qw( gather_data_from_fh open_data_file );
# Automatically export everything allowed.
# Generally best to leave empty, but in some cases it makes
# sense to export a small number of subroutines automatically.
our @EXPORT = @EXPORT_OK;
# Array of directories to search for files.
our @SEARCH_PATH;
# Parse the contents of a IO::Handle object and return structured data
sub gather_data_from_fh {
my $fh = shift;
my %data;
while( my $line = $fh->readline );
# Parse the line
chomp $line;
my ($key, @values) = split $line;
$data{$key} = \@values;
}
return \%data;
}
# Search a list of directories for a file with a matching name.
# Open it and return a handle if found.
# Die otherwise
sub open_data_file {
my $file_name = shift;
for my $path ( @SEARCH_PATH, '.' ) {
my $file_path = "$path/$file_name";
next unless -e $file_path;
open my $fh, '<', $file_path
or die "Error opening '$file_path' - $!\n"
return $fh;
}
die "No matching file found in path\n";
}
1; # Need to have trailing TRUE value at end of module.
现在在脚本 A 中,我们获取一个文件名来搜索和处理,然后打印格式化输出:
use strict;
use warnings;
use MyModule;
# Configure which directories to search
@MyModule::SEARCH_PATH = qw( /foo/foo/rah /bar/bar/bar /eeenie/meenie/mynie/moe );
#get file name from args.
my $name = shift;
my $fh = open_data_file($name);
my $data = gather_data_from_fh($fh);
for my $key ( sort keys %$data ) {
print "$key -> ", join ', ', @{$data->{$key}};
print "\n";
}
脚本 B,搜索文件,对其进行解析,然后将解析后的数据结构写入 YAML 文件。
use strict;
use warnings;
use MyModule;
use YAML qw( DumpFile );
# Configure which directories to search
@MyModule::SEARCH_PATH = qw( /da/da/da/dum /tutti/frutti/unruly /cheese/burger );
#get file names from args.
my $infile = shift;
my $outfile = shift;
my $fh = open_data_file($infile);
my $data = gather_data_from_fh($fh);
DumpFile( $outfile, $data );
一些相关文档:
use
。其中一些文档假设您将在 CPAN 上共享您的代码。如果您不会发布到 CPAN,只需忽略有关注册和上传代码的部分。
即使您不是为 CPAN 编写代码,使用标准工具和 CPAN 文件结构进行模块开发也是有益的。遵循该标准,您可以使用 CPAN 作者使用的所有工具来简化开发、测试和安装过程。
我知道这一切看起来真的很复杂,但标准工具让每一步都很容易。借助可用的出色工具,即使将单元测试添加到您的模块分发中也很容易。回报是巨大的,非常值得您投入时间。
有时有一个单独的脚本是有意义的,有时它没有。“黑魔法”并不复杂。
#!/usr/bin/perl
# square.pl
use strict;
use warnings;
my $input = shift;
print $input ** 2;
#!/usr/bin/perl
# sum_of_squares.pl
use strict;
use warnings;
my ($from, $to) = @ARGV;
my $sum;
for my $num ( $from .. $to ) {
$sum += `square.pl $num` // die "square.pl failed: $? $!";
}
print $sum, "\n";
使用 IPC::System::Simple 可以自动更轻松、更好地报告故障错误:
#!/usr/bin/perl
# sum_of_squares.pl
use strict;
use warnings;
use IPC::System::Simple 'capture';
my ($from, $to) = @ARGV;
my $sum;
for my $num ( $from .. $to ) {
$sum += capture( "square.pl $num" );
}
print $sum, "\n";