皮条客我的Perl代码

时间:2010-01-19 12:53:02

标签: perl refactoring coding-style

我是一位经验丰富的开发人员,但不是Perl。我通常学习Perl来破解脚本,然后我再次忘记它直到下一次。因此,我正在寻找专业人士的建议。

这次我正在构建一系列数据分析脚本。大致简化,程序结构如下:

01 my $config_var = 999;

03 my $result_var = 0;

05 foreach my $file (@files) {
06   open(my $fh, $file);
07   while (<$fh>) {
08     &analyzeLine($_);
09   }
10 }

12 print "$result_var\n";

14 sub analyzeLine ($) {
15   my $line = shift(@_);
16   $result_var = $result_var + calculatedStuff;
17 }

在现实生活中,最多有六个不同的config_varresult_var s。

这些脚本的主要区别在于分配给config_var的值。主循环在每种情况下都是相同的,analyzeLine()将大致相同,但可能会有一些小变化。

我可以通过制作此代码的N份副本来实现我的目的,并且在这里和那里进行小的改动;但这严重违反了各种良好设计规则。理想情况下,我想编写一系列脚本,其中只包含一组config var初始化,然后是

do theCommonStuff;

请注意,config_var(及其兄弟姐妹)必须可用于公共代码,必须result_var及其相似之处analyzeLine()进行一些计算。

我应该将“常用”代码打包到模块中吗?创建一个类?使用全局变量?

虽然不完全是代码高尔夫,但我正在寻找一种简单,紧凑的解决方案,这将使我能够只为差异进行干洗和编写代码。我想我宁愿不从包含所有配置的巨大表中驱动代码,当然也不能使它适应数据库使用。

期待您的建议,谢谢!


更新

因为有人问,这是真实的 analyzeLine

# Update stats with time and call data in one line.
sub processLine ($) {
  my $line = shift(@_);
  return unless $line =~ m/$log_match/;
  # print "$1 $2\n";
  my ($minute, $function) = ($1, $2);
  $startMinute = $minute if not $startMinute;
  $endMinute = $minute;
  if ($minute eq $currentMinute) {
    $minuteCount = $minuteCount + 1;
  } else {
    if ($minuteCount > $topMinuteCount) {
      $topMinute = $currentMinute;
      $topMinuteCount = $minuteCount;
      printf ("%40s %s : %d\n", '', $topMinute, $topMinuteCount);
    }
    $totalMinutes = $totalMinutes + 1;
    $totalCount = $totalCount + $minuteCount;
    $currentMinute = $minute;
    $minuteCount = 1;
  }
}

由于这些变量在很大程度上是相互依赖的,我认为单独计算的功能解决方案是不切实际的。我为误导人道歉。

5 个答案:

答案 0 :(得分:10)

两条评论:首先,不要张贴行号,因为它们使复制,粘贴和编辑变得更加困难。其次,不要使用&func()来调用sub。见perldoc perlsub

  

可以使用显式&前缀调用子例程。 &在现代Perl中是可选的,... &形式不仅使参数列表可选,而且还禁用对您提供的参数的任何原型检查。

简而言之,使用&可能会令人惊讶,除非您知道自己在做什么以及为什么要这样做。

此外,Perl中的don't use prototypes。它们与其他语言的原型不同,并且除非您知道自己在做什么,否则可以使用 非常 令人惊讶的效果。

不要忘记检查系统调用的返回值,例如open。将autodie与现代perl s。

一起使用

针对您的特定问题,请在哈希中收集所有配置变量。将该哈希值传递给analyzeLine

#!/usr/bin/perl

use warnings; use strict;
use autodie;

my %config = (
    frobnicate => 'yes',
    machinate  => 'no',
);

my $result;
$result += analyze_file(\%config, $_) for @ARGV;

print "Result = $result\n";

sub analyze_file {
    my ($config, $file) = @_;

    my $result;

    open my $fh, '<', $file;
    while ( my $line = <$fh> ) {
        $result += analyze_line($config, $line);
    }

    close $fh;

    return $result;
}

sub analyze_line {
    my ($line) = @_;
    return length $line;
}

当然,您会注意到$config正在遍布整个地方,这意味着您可能希望将其转换为OO解决方案:

#!/usr/bin/perl

package My::Analyzer;

use strict; use warnings;

use base 'Class::Accessor::Faster';

__PACKAGE__->follow_best_practice;
__PACKAGE__->mk_accessors( qw( analyzer frobnicate machinate ) );

sub analyze_file {
    my $self = shift;
    my ($file) = @_;

    my $result;

    open my $fh, '<', $file;
    while ( my $line = <$fh> ) {
        $result += $self->analyze_line($line);
    }

    close $fh;

    return $result;
}

sub analyze_line {
    my $self = shift;
    my ($line) = @_;
    return $self->get_analyzer->($line);
}

package main;

use warnings; use strict;
use autodie;

my $x = My::Analyzer->new;

$x->set_analyzer(sub {
        my $length; $length += length $_ for @_; return $length;
});
$x->set_frobnicate('yes');
$x->set_machinate('no');


my $result;
$result += $x->analyze_file($_) for @ARGV;

print "Result = $result\n";

答案 1 :(得分:6)

继续创建一个类层次结构。您的任务是OOP编程风格的理想选择。 这是一个例子:

package Common;
sub new{
  my $class=shift;
  my $this=bless{},$class;
  $this->init();
  return $this;
}
sub init{}
sub theCommonStuff(){ 
  my $this=shift;
  for(1..10){ $this->analyzeLine($_); }
}
sub analyzeLine(){
  my($this,$line)=@_;
  $this->{'result'}.=$line;
}

package Special1;
our @ISA=qw/Common/;
sub init{
  my $this=shift;
  $this->{'sep'}=',';   # special param: separator
}
sub analyzeLine(){      # modified logic
  my($this,$line)=@_;
  $this->{'result'}.=$line.$this->{'sep'};
}

package main;
my $c = new Common;
my $s = new Special1;
$c->theCommonStuff;
$s->theCommonStuff;
print $c->{'result'}."\n";
print $s->{'result'}."\n";

答案 2 :(得分:2)

如果所有公共代码都在一个函数中,那么将配置变量作为参数并返回结果变量(作为返回值或作为输入/输出参数)的函数将会执行。否则,制作课程(“包”)也是一个好主意。

sub common_func {
    my ($config, $result) = @_;
    # ...
    $result->{foo} += do_stuff($config->{bar});
    # ...
}

在上面注意,配置和结果都是哈希值(实际上是对它的引用)。您可以使用您认为适合您目标的任何其他数据结构。

答案 3 :(得分:2)

一些想法:

  • 如果有多个$result_var,我建议创建一个单独的子程序来计算每个。
  • 如果子程序依赖于该函数之外的信息,则应将其作为参数传递给该子例程,而不是依赖于全局状态。
  • 或者将整个事物包装在一个类中,$result_var作为类的属性。

实际上,有几种方法可以实现这一点:

(1)让你的&analyzeLine函数返回calculatedStuff,并在函数外的循环中将其添加到&result_var

  $result_var = 0;
  foreach my $file (@files) {
      open(my $fh, $file);
          while (<$fh>) {
              $result_var += analyzeLine($_);
          }
      }
  }

  sub analyzeLine ($) {
      my $line = shift(@_);
      return calculatedStuff;
  }

(2)明确地将$result_var传递给analyzeLine,然后返回已更改的$result_var

  $result_var = 0;
  foreach my $file (@files) {
      open(my $fh, $file);
          while (<$fh>) {
              $result_var = addLineToResult($result_var, $_);
          }
      }
  }

  sub addLineToResult ($$) {
      my $running_total = shift(@_);
      my $line = shift(@_);
      return $running_total + calculatedStuff;
  }

重要的是,如果你为几个$ result_vars中的每一个分开函数,你将更容易编写干净的代码。不要担心优化。当你的代码证明自己很慢时,这可能会在以后发生。改进的设计将在时机成熟时使优化变得更容易。

答案 4 :(得分:0)

为什么不创建一个函数并使用$ config_var和$ result_var作为参数?