我是Perl的新手,并且想知道关于子程序的最佳实践是关于Perl的。子程序可以太大吗?
我正在编写一个脚本,它可能需要调用另一个脚本。我应该以子程序的形式将旧脚本集成到新脚本中吗?我需要将一个参数传递给脚本并需要一个返回值。
我猜我必须做一些黑魔法才能从原始脚本中获取输出,所以子程序有意义吗?
答案 0 :(得分:7)
编写代码时,避免“黑魔法”总是一个好主意。你永远不想跳过箍,并提出一个不直观的黑客来解决问题,特别是如果以后需要支持该代码。诚然,它确实发生了,我们都对此感到内疚。情况可能会严重影响“只是让工作变得有用。”
关键是,最佳做法始终是使代码清晰易懂。请记住,根据我的经验,使用Perl代码尤其为真,您在几个月前编写的任何代码都可能是由其他人编写的。因此,即使你是唯一需要支持它的人,也要帮自己一个忙,让它易于阅读。
不要坚持广泛的想法,比如“赞成更多文件覆盖更大的文件”或“赞成更小的方法/子程序而不是更大的文件”等等。这些都是很好的指导方针,但是应用指南的精神而不是这封信。保持代码清洁,易懂和可维护。如果这意味着偶尔的大文件或大型方法/子程序,那就这样吧。只要它有意义。
答案 1 :(得分:5)
关键设计目标是分离关注点。理想情况下,每个子例程执行一个明确定义的任务。从这个角度来看,主要问题不在于子程序的大小,而在于它的重点。如果您的程序需要多个任务,则意味着需要多个子程序。
在更复杂的场景中,最终可能会出现逻辑上属于一起的子程序组。它们可以组织成库,甚至更好的模块。如果可能,您希望避免最终需要相互通信的多个脚本的情况,因为一个脚本将数据返回到另一个脚本的常用机制很繁琐:第一个脚本写入标准输出,第二个脚本写入标准输出脚本必须解析该输出。
几年前,我开始从事一项工作,要求我构建大量的命令行脚本(至少,结果如何;最初,我们正在构建的内容尚不清楚)。我当时很缺乏经验,并没有很好地组织代码。事后看来,我应该从我编写模块而不是脚本的前提出发。换句话说,真正的工作将由模块完成,并且脚本(用户在命令行上执行的代码)将保持非常小的前端以便以各种方式调用模块。这将有助于代码重用和所有这些好东西。生活和学习,对吗?
答案 2 :(得分:3)
在脚本中重用代码时尚未提及的另一个选项是将公共代码放在模块中。如果将共享子例程放入一个或多个模块中,则可以保持脚本简短并专注于他们所做的特殊操作,同时以易于访问和重用的形式隔离公共代码。
例如,这是一个包含几个子程序的模块。将其放在名为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
上的perlfunc文章。其中一些文档假设您将在CPAN上共享代码。如果您不想发布到CPAN,只需忽略有关注册和上传代码的部分。
即使您不是在为CPAN编写,也可以使用标准工具和CPAN文件结构进行模块开发。遵循该标准允许您使用CPAN作者使用的所有工具来简化开发,测试和安装过程。
我知道所有这一切看起来都很复杂,但标准工具使每一步都变得简单。由于提供了出色的工具,即使将单元测试添加到模块分发中也很容易。收益巨大,非常值得您投资。
答案 3 :(得分:0)
有时候有一个单独的脚本是有意义的,有时却没有。 “黑魔法”并不复杂。
#!/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";