Прокачай мой Perl код - PullRequest
       19

Прокачай мой Perl код

4 голосов
/ 19 января 2010

Я опытный разработчик, но не в 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_var с и result_var с.

Эти сценарии отличаются в основном значениями, присвоенными config_var с. Основной цикл будет одинаковым во всех случаях, и analyzeLine() будет в основном одинаковым, но может иметь небольшие отклонения.

Я могу достичь своей цели, сделав N копий этого кода с небольшими изменениями здесь и там; но это грубо нарушает все виды правил хорошего дизайна. В идеале я хотел бы написать серию сценариев, содержащих только набор инициализаций config var, за которыми следует

do theCommonStuff;

Обратите внимание, что config_var (и его братья и сестры) должны быть доступны общему коду, как и result_var и его аналоги, по которым analyzeLine() выполняет некоторые вычисления.

Должен ли я упаковать свой "общий" код в модуль? Создать класс? Использовать глобальные переменные?

Хотя это не совсем код игры в гольф, я ищу простое и компактное решение, которое позволит мне СУХОЙ и писать код только для различий. Я думаю, что я бы предпочел не выгружать код из огромной таблицы, содержащей все конфиги, и, конечно, не адаптировать его для использования базы данных.

Ждем ваших предложений и спасибо!


Обновление

Так как люди спрашивали, вот real 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 ]

10 голосов
/ 19 января 2010

Два комментария: во-первых, не публикуйте номера строк, поскольку они затрудняют копирование, вставку и редактирование.Во-вторых, не используйте &func() для вызова подпрограммы.См. perldoc perlsub :

Подпрограмма может быть вызвана с использованием явного префикса &.& не обязателен в современном Perl, ... Форма & не только делает список аргументов необязательным, но также отключает любую проверку прототипа по аргументам, которые вы предоставляете.

Короче говоря, использование & может быть удивительным, если вы не знаете, что вы делаете и почему вы это делаете.

Кроме того, не используют прототипы в Perl.Они не совпадают с прототипами на других языках и, опять же, могут иметь очень удивительных эффектов, если вы не знаете, что делаете.

Не забудьте проверитьвозвращаемое значение системных вызовов, таких как 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";
6 голосов
/ 19 января 2010

Идем дальше и создаем иерархию классов.Ваша задача - идеальная площадка для программирования в стиле ООП.Вот пример:

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 голосов
/ 19 января 2010

Некоторые мысли:

  • Если их несколько $result_var, я бы рекомендовал создать отдельную подпрограмму для вычисления каждой из них.
  • Если подпрограмма полагается на информацию вне этой функции, она должна передаваться в качестве параметра этой подпрограмме, а не полагаться на глобальное состояние.
  • В качестве альтернативы можно обернуть все это в классе,с $result_var в качестве атрибута класса.

Практически, есть несколько способов, которыми вы могли бы реализовать это:

(1) Ваша функция &analyzeLine return 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, вы сможете с большей готовностью писать чистый код.Пока не беспокойтесь об оптимизации.Это может произойти позже, когда ваш код оказался медленным.Улучшенный дизайн облегчит оптимизацию, когда придет время.

2 голосов
/ 19 января 2010

Если весь общий код находится в одной функции, то функция, принимающая переменные конфигурации в качестве параметров и возвращающая результирующие переменные (либо в качестве возвращаемых значений, либо в качестве параметров ввода / вывода), будет делать это. В противном случае создание класса («пакета») также является хорошей идеей.

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

Обратите внимание, что и конфиг, и результат являются хешами (на самом деле ссылки на них). Вы можете использовать любую другую структуру данных, которая, по вашему мнению, будет соответствовать вашей цели.

0 голосов
/ 19 января 2010

почему бы не создать функцию и использовать $ config_var и $ result_var в качестве параметров?

...