perl — мой стиль написания программ

Perl умирает. В этом почти нет сомнений. Но так как он это делает уже много лет, отказываться от него преждевременно. В этой статье пойдёт речь о том, как немного облегчить себе жизнь при написании перловых программ.

Некоторое время назад перловое сообщество начало оправляться после диверсии perl6 и взялось за модернизацию perl5. Ниже то, что мне приглянулось из нововведений именно на практике. Под практикой я понимаю написание программ для перла, входящего в стандартный набор Debian Jessie (5.20), так как последние версии перла и малосовместимы со сторонним софтом типа Eclipse EPIC, и требуют ручной установки.

perl5i::2

perl5i::2 - набор модулей, которые меняют стандартное поведение и привносят новые возможности. Среди них отмена автовивификации (autovivification) - автоматического создания несуществующих ключей в хэше; добавление к обычным объектам новой функциональности - например

#!perl
$string->trim;
12->is_integer;

, utf8::all - включение всех utf8 возможностей (feature); добавление Try::Tiny; включение use strict, use warnings, given/when/say; превращение time() в объект DateTime, подключение Modern::Perl.

вивификация (vivification) - оживление, создание пустого хэша если его нет :)))

Превращение time() в объект DateTime

Потенциально опасная возможность, так как после этого time() становится несовместимым со старым кодом и производит некоторые трудноотлавливаемые ошибки. Я отключаю эту возможность:

#!perl
use perl5i::2 -skip => ['time'];

Так можно отключить другие фичи. Среди них есть некоторые сомнительные, такие как возможность определения параметров функций, которые несовместимы с синтаксическими анализаторами (EPIC, perltidy):

#!perl
func hello($place) {
    say "Hello, $place!\n";
}

Правда, (Perl-Tidy-Sweetened)[https://github.com/mvgrimes/perl-tidy-sweetened] с ними может и справиться.

Try::Tiny - замена eval

Try::Tiny позволяет писать в стиле

#!perl
try {
    $dbh->do("delete from all_tables->cascade");
    $dbh->commit();
    say("Вот и всё, ребята!");
}
catch {
    say $_; # $_ - то что было в $@ (почти)
    $dbh->rollback();
}
finally {
};

Есть более-менее приемлемое решение для return из try-catch.

Try::Tiny автоматически подключается при использовании perl5i:2

Log::Log4perl

Log::Log4perl приводит в порядок логи. Это копия Log4j из Java мира. Многие модули, для которых актуально создание логов, интегрированы с ним, например стандартная система логирования в Mojolicious может быть заменена на MojoX::Log::Log4perl. Наверно, самая мощная система конфигурирования логов.

К примеру, возможен такой трюк: при обработке нескольких объектов удобно логи для каждого объекта выводить в отдельный файл. Например так:

#!perl
sub logger_file_switch {
  my $appender_name = shift;
  my $id_or_file    = shift;
  my ($filename_old, $filename_new);
  my $appender = Log::Log4perl->appender_by_name($appender_name);
  if (UNIVERSAL::isa($appender, 'Log::Log4perl::Appender::File')) {
      $filename_old = $filename_new = $appender->filename();
      if ($id_or_file =~ /^\d+$/o) {
          $filename_new =~ s{/[^/]*$}{/$id_or_file.log};
      }
      else{
          $filename_new = $id_or_file; # это файл, на который переключиться
      }
      $appender->file_switch($filename_new);
  }
  else {
      die("Appender с указанным именем '$appender_name' не найден");
  }
  return ($filename_old, $filename_new) if wantarray;
  return [$filename_old, $filename_new];
}

my ($filename_old , $filename_new);
try {
    # Переключаемся
    ($filename_old , $filename_new) = $self->logger_file_switch('any_appender', $id); 
  }
finally {
    # Возвращаем обратно
    $self->logger_file_switch('any_appender', $filename_old)
};

Try::Tiny и Log::Log4perl вместе

Try::Tiny маскирует точку возникновения ошибки. Некоторую компенсацию этого предлагает метод wrapper_register:

#!perl
Log::Log4perl->init_once(\$logger_conf);
Log::Log4perl->wrapper_register("Try::Tiny");

use feature 'say'

say не делает ничего отличного от print, кроме добавления "\n" в конце строки.

say автоматически подключается при использовании perl5i:2

use feature 'switch'

Разрешает использование конструкции given/when

#!perl
for ($var) {
    $abc = 1 when /^abc/;
    $def = 1 when /^def/;
    default { $nothing = 1 }
}

switch автоматически подключается при использовании perl5i:2

DateTime

DateTime поддерживает объекты, хранящие дату и время, интервалы между временами, операции типа добавления/вычитания и так далее. Rose::DB::Object и DBIx::Class для работы с временными типами используют именно его. Позволяет работать с широким диапазоном дат (unix timestamp - даты от 1970 до 2037 ). Есть куча парсеров форматов дат, возвращающих объект DateTime, например DateTime::Format::Builder, позволяющий быстро написать свой парсер.

DateTime автоматически подключается при использовании perl5i:2, но, кроме этого, есть побочный эффект - Превращение time() в объект DateTime. Эффект отключаемый.

У DateTime есть одна очень неприятная особенность - он медленный. Time::Moment имеет меньшую функциональность, но на реальном тесте показывает в десятки раз большую производительность. На синтетических тестах он вообще неимоверно крут - 12743% от DateTime.

Mail::Box

Для работы с почтой есть огромное количество модулей. Однажды Mark Overmeer получил небольшой грант и довёл до ума Mail::Box. Почти довёл, так как грант кончился на IMAP, который остался недоделан (точнее, не учитывает всех многочисленных реализаций, так-то он более-менее рабочий).

LockFile::Simple - блокирование запуска второй копии программы

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

Типовое использование (есть куча других авриантов):

use LockFile::Simple;

my $lockformat = "lalala-%f.lock";

# _lock возвратит 1 если удастся создать блокировку ресурса с именем 'foo' 
# и 0 если он уже заблокирован
sub _lock('foo') {
  my $name = shift;
  # для того, чтобы установить флаг -stale => 1 , создаём менеджер.
  # -stale => 1 заставит удалять старые файлы блокировки, 
  # процессы для которых уже померли без разблокировки.
  # -autoclean => 1 автоматически снимет блокировку при завершении программы.
  # это полезно, так как -stale=>1 вызовет дополнительные затраты ресурсов.
  my $lockmgr = LockFile::Simple->make(
                -stale => 1, 
                -format => $lockformat,
                -autoclean =>1,
  );
  # trylock() либо блокирует, либо сообщает что уже заблокировано.
  # более сложный алоритм позволяет пытаться заблокировать какое-то время,
  # производя несколько попыток.
  if ($lockmgr->trylock($name)) {
    warn "Lock $name Ok";
    return 1;
  }
  warn "Lock $name failed"
  return 0;
}

# _unlock('foo') снимет блокировку с ресурса 'foo'
# если не снять блокировку, то она автоматически будет снята при завершении процесса
# при указании -autoclean=>1 или при указании -stale=>1 будет снята при попытке заблокировать
# новым процессом тот же ресурс
sub _unlock {
  my $name = shuft;
  my $lockmgr = LockFile::Simple->make();
  my $lockfile = $lockmgr->lockfile($name, $locktemplate);
  $lockmgr->unlock($lockfile);
  warn "Unlock $lockfile";
}

Perl-Tidy-Sweetened

Modern Perl плавно поменял свой синтаксис, а perltidy этого не заметил. Есть замена-дополнение Perl-Tidy-Sweetened, штатными для perltidy методами немного исправляюший ситуацию.

Leave a Reply