皮条客我的Perl代码

皮条客我的Perl代码,perl,refactoring,coding-style,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 &anal

我是一名经验丰富的开发人员,但不是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\u var
(及其同级)必须可用于公共代码,
result\u var
及其外观类也必须可用于公共代码,
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;
  }
}

由于这些变量在很大程度上是相互依赖的,我认为一个单独计算的函数解决方案是不实际的。很抱歉误导了人们。

请继续创建类层次结构。您的任务是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";

为什么不创建一个函数,并使用$config\u var和$result\u var作为参数?

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

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

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

两条注释:第一,不要发布行号,因为行号会使复制、粘贴和编辑变得非常困难。其次,不要使用
&func()
调用子对象。请参阅:

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

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

另外,在Perl中。它们与其他语言中的原型不同,而且,除非你知道自己在做什么,否则它们可能会产生非常令人惊讶的效果

不要忘记检查系统调用的返回值,例如
open
。与现代
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;
}
一些想法:

  • 如果有几个
    $result\u var
    s,我建议创建一个单独的子例程来计算每个子例程
  • 如果子例程依赖于该函数外部的信息,则应将其作为参数传递给该子例程,而不是依赖于全局状态
  • 或者将整个内容包装在一个类中,并将
    $result\u var
    作为该类的属性
实际上,有几种方法可以实现这一点:

(1) 让您的
&analyzeLine
函数返回
calculatedStuff
,并将其添加到函数外部的循环中的
&result\u var

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

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

重要的一点是,如果您为几个$result_变量中的每一个分离函数,您将更容易编写干净的代码。不要担心优化问题。这可能会在以后,当您的代码被证明自己很慢时出现。改进后的设计将使优化更加容易。

每种设计都有几个,我必须将引用传递给
结果\u var
s。另外,如果函数位于单独的文件中,如何从“主”脚本访问它?听起来不错,看起来也不错,谢谢!我可以将这些包放在单独的文件中,每个文件的名称都与包类似,对吗?请不要使用,也不要推荐间接对象语法,不管它表面上多么吸引人,它是能够编写
new Common
,而不是
Common->new
@Carl Smotricz:你问了这个问题,因此,选择一个答案而不是另一个答案,不要感到难过。请注意,@catwalk访问对象的内部(违反了封装),而不是使用getter和setter,我使用
Class::Accessor::Faster
的唯一原因是为了避免编写访问器;-)哦,当然,允许模块用户指定一个子模块来对每行进行实际的“分析”,这给了您灵活性,并将脚手架和实际工作分离开来。要获得优秀的OOP效果和较少的样板文件,请查看Moose。你能给我们一个实际的例子,说明你想用这个来完成什么吗?这看起来是一个使用函数式编程的好机会。我是出于习惯而创建原型的,但是是的,它是可选的。@Chris Jester Young看到了,我不知道-1是否来自您,是否超过了原型,但如果是这样,撤回否决票将是非常感激的。(如果是为了别的什么,我很想听听。):-)@ChrisJester-Young:不,这只是原型问题。“我只是暂时不这么做了。”思南:谢谢,非常感谢!谢谢你的评论!我加入了行号,以便更容易地讨论代码,正如我所期望的那样,会有更抽象的讨论;另外,我使用&来明确我调用的是自定义函数。但我当然可以不用这两个。我确实在我的
open
上使用了
或die
,也在
opendir
上,谢谢。再一次,对于我的例子过于简单表示歉意。禁用我的perl中的
autodie
#!/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";
  $result_var = 0;
  foreach my $file (@files) {
      open(my $fh, $file);
          while (<$fh>) {
              $result_var += analyzeLine($_);
          }
      }
  }

  sub analyzeLine ($) {
      my $line = shift(@_);
      return calculatedStuff;
  }
  $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;
  }