Pull to refresh

Mojolicious Perl Style

Reading time 9 min
Views 10K
Хочу описать стиль программирования на языке Perl, к которому я стремлюсь и который в основном перенял от современного web-фреймворка Mojolicious, но наверное много где еще применяется подобный. Мне кажется выработать правильный стиль кодинга — очень важно.

Пример 1:
Методы в одну строку.
Если обращение к каждому аргументу функции происходит лишь один раз, и порядок применения их в коде соответствует порядку переданных аргументов, то предлагается извлекать их с помощью стандартной функции shift, которая если вызывается без аргументов, по-умолчанию работает с массивом @_, в котором хранятся все переданные аргументы функции, выталкивает первый аргумент из массива и возвращает его.
sub node { shift->tree->[0] }
#
sub parse { shift->_delegate(parse => shift) }
#
sub _tag { shift->new->tree(shift)->xml(shift) }

Пример 2:
Сначала извлекаем первый параметр, имя класса например, все остальные аргументы передаем другой функции и пусть она их обрабатывает.
sub tree { shift->_delegate(tree => @_) } 
# т.е. может превратиться в это _delegate(tree => [], deep => 5) или это _delegate(tree => [], 5, 0) 
sub log { shift->emit('message', lc shift, @_) }

Пример 3:
Тоже для метода в одну строчку.
Здесь происходит обращение к одному и тому же аргументу целых 3 раза, потому для доступа к аргументу используется прямое обращение к элементу массива аргументов $_[0].
sub _instance { ref $_[0] ? $_[0] : $_[0]->singleton }
#
sub find { $_[0]->_collect(@{$_[0]->_css->select($_[1])}) }


Пример 4:
Объявлять переменную можно в условном однострочном блоке if, else if или unless, а затем в случае не выполнения условия, код продолжится дальше выполняться и новую объявленную переменную там затем можно будет использовать.
sub at {
  my $self = shift;
  return undef unless my $result = $self->_css->select_one(@_);
  return _tag($self, $result, $self->xml);
}

Пример 5:
Объявление переменной и тут же ее использование в условии.
return '' if (my $tree = $self->tree)->[0] ne 'tag';
return $tree->[1] unless $type;
#
return if !(my $expires = delete $session->{expires}) && $expiration;

Пример 6:
Очень полезный модуль Mojo::EventEmitter, кстати точно такой же есть в Node JS, он там тоже один из ключевых модулей, выполняет такой же функционал, только по-моему в Mojolicious его код выглядит значительно проще и короче. Можно его использовать в своих проектах, которые даже не используют этот фреймворк. Его необходимо сделать базовым для своего модуля и его помощью можно добавлять методы для событий, а внутри какого-то своего метода их вызывать с помощью emit.
package Cat;
use Mojo::Base 'Mojo::EventEmitter';
# Emit events
sub poke {
  my $self = shift;
  $self->emit(roar => 3);
}
package main;
# Subscribe to events
my $tiger = Cat->new;
$tiger->on(roar => sub {
  my ($tiger, $times) = @_;
  say 'RAWR!' for 1 .. $times;
});
$tiger->poke;

Пример 7:
Устранение утечки памяти. Например в модуле Mojo::EventEmitter, есть метод once, который мог бы быть причиной утечки памяти, если бы не использовал функцию weaken.
Если вы имеете ссылку на функцию, которая сама как либо использует ту переменную, которая ссылается на нее (прямо или опосредовано) — тут надо применять weaken (Scalar::Util), иначе будет утечка памяти!
sub once {
  my ($self, $name, $cb) = @_;
  weaken $self;
  my $wrapper;
  $wrapper = sub {
    $self->unsubscribe($name => $wrapper);
    $cb->(@_);
  };
  $self->on($name => $wrapper);
  weaken $wrapper;
  return $wrapper;
}
# или вот
sub timeout {
  my $self = shift;
  #
  weaken $self;
  $self->{timer} = $reactor->timer($timeout => sub { $self->emit_safe('timeout')->close });
  return $self;
}

Пример 8:
Срез массива.
$_ eq $child ? last : $i++ for @$parent[$i .. $#$parent];

Пример 9:
Можно выполнить одно и тоже действие для нескольких переменных в одной строке, указав их через запятую в цикле for.
s/\%20/\+/g for $name, $value;

Пример 10:
Присвоение одного значения сразу нескольким переменным. К тому же очень удобно значение переменной класса или значение хэша присвоить простой переменной, которая короче записывается и затем ее использовать в функции или блоке, проще например ее использовать при разыменовании ссылок на массив или хэш, без лишних фигурных скобок.
my $params = $self->{params} = [];
return $params unless length $str;

Пример 11:
Если в блоках if, else if или else код простой из одного выражения, то лучше и условие и блок записать в одной строчке.
if ($params->[$i] eq $name) { splice @$params, $i, 2 }
else { $i += 2 }

Пример 12:
Использование Mojo::Base в качестве базового для своего модуля. Подключает по умолчанию ряд прагм (модулей) таких как strict, warnings, utf8 и новшества Perl 5.10
Добавляет нашему классу метод has, похожий стиль из Moose, котовый можно использовать для создания переменных класса, нижеуказанным способом.
Если конструктору класса передать хэш, как в последней строке примера, то он инициализирует переменные объекта.
package Foo;
use Mojo::Base -base;

has [qw(kept_alive local_address original_remote_address remote_port)];
 # сразу несколько не инициализированных переменных класса в одной строке
has req => sub { Mojo::Message::Request->new };
has max => 4; # только числа и строки можно указывать без анонимной функции
has str => 'Hi!';
has list => sub { [] }; # ссылка на массив только через анонимную функцию
has dict => sub { {} };  # ссылка на хэш только через анонимную функцию
has hint => <<EOF; # многострочный текст
See 'APPLICATION help COMMAND' for more information on a specific command.
EOF
#
my $Foo = Foo->new({kept_alive =>$kept_alive, local_address => $local_address});

Пример 13:
Метод базового класса, который необходимо переопределить в дочернем.
use Carp 'croak';
sub server_write { croak 'Method "server_write" not implemented by subclass' }

Пример 14:
Константы, несколько или одна.
use constant {
  PC_BASE => 36,
  PC_TMIN => 1,
  PC_TMAX => 26
};
use constant PC_BASE => 36;

Пример 15:
Строку содержащую переменную внутри и двойные кавычки, лучше записать так.
die qq{Unknown command "$name", maybe you need to install it?\n}
  unless $module;
#
sub quote {
  my $str = shift;
  $str =~ s/(["\\])/\\$1/g;
  return qq{"$str"};
}

Пример 16:
Создание переменной хэша, а затем передача его в функцию. Большой хэш лучше создавать отдельно, а не прямо в аргументе анонимный, так удобней.
my $options = {
  domain => $self->cookie_domain,
  expires => $session->{expires},
  httponly => 1,
  path => $self->cookie_path,
  secure => $self->secure
};
$c->signed_cookie($self->cookie_name, $value, $options);

Пример 17:
Если в однострочном блоке-условии создана переменная и планируется ее использовать в теле-блока, то это не получится сделать, она будет недоступна, потому надо переменную объявить до блока.
Во втором случае в примере при использовании созданной переменной в блоке используются фигурные скобки, так же показан там пример извлечения значения из хэша по ключу, если возможно хэш еще не создан.
// — оператор который возвращает истину, если одно из выражений определено, т.е. не равно undef.
my $new;
unless ($new = $self->_class($old, $field)) { return !!defined $new }
#
if (my $code = ($tx->req->error // {})->{advice}) { $res->code($code) }
#
my $stash = {};
if (my $sub = $tx->can('stash')) { ($stash, $tx) = ($tx->$sub, $tx->tx) }

Пример 18:
Выполнение одной и той же функции с разными аргументами, там где значения массива простые слова, лучше воспользоваться стандартной qw функцией, не надо отделять каждый элемент запятой, так короче и красивее.
Во втором случае в примере использование функции qw если слишком много элементова массива надо задать вручную, лучше разделить на несколько строк, через запятую и в каждой строке вызывать эту функцию.
$self->plugin($_)
  for qw(HeaderCondition DefaultHelpers TagHelpers EPLRenderer EPRenderer);
#
my %NORMALCASE = map { lc($_) => $_ } (
  qw(Accept Accept-Charset Accept-Encoding Accept-Language Accept-Ranges),
  qw(Allow Authorization Cache-Control Connection Content-Disposition),
# ...
);
# если необходимо использовать созданную переменную с блоке
for my $name (qw(app flash param stash session url_for validation)) {
  $app->helper($name => sub { shift->$name(@_) });
}

Пример 19:
Часто используется такая конструкция условия, если первое истина, а второе ложь, чтобы не выполнялось второе условие, если первое ложь. Во втором выражении примера создается переменная $class, если она не пустая, а второе выражение ложное, то выходим из функции, если второе выражение истина, то продолжаем выполнение и в этом коде сможем воспользоваться этой переменной.
return $self->emit(read => $chunk)
  unless $self->is_compressed && $self->auto_relax;
#
return unless (my $class = ref $self || $self) && $attrs;
#
return unless $self->cleanup && defined(my $path = $self->path);

Пример 20:
Написание однострочных выражений, которые не помещаются даже на двух строчках.
warn
  qq/Problem loading URL "@{[$tx->req->url->to_abs]}". ($err->{message})\n/
  if $err && !$err->{code};
#
$app->helper($_ => $self->can("_$_"))
  for qw(accepts content content_for csrf_token current_route delay),
  qw(inactivity_timeout is_fresh url_with);
#
return $self->error(
  {message => 'Maximum buffer size exceeded', advice => 400})
  if $self->content->is_limit_exceeded;

Пример 21:
Использование eval напрмер для проверки существования модуля.
eval "use Mojolicious::Lite; 1" or die $@;
#
use constant IPV6 => $ENV{MOJO_NO_IPV6}
  ? 0
  : eval 'use IO::Socket::IP 0.20 (); 1';

Пример 22:
Добавление нового метода для класса, no strict и no warnings действуют только локально, в функции или блоке. Так же стоит отметить, что символические ссылки работают только с глобальными переменными, что нам и надо тут.
sub monkey_patch {
  my ($class, %patch) = @_;
  no strict 'refs'; # позволить использовать символические ссылки
  no warnings 'redefine'; # не выводим ворнинг, если происходит переопределение метода
  *{"${class}::$_"} = $patch{$_} for keys %patch;
}

Пример 23:
Модуль ojo.pm, созданный специально, чтоб использовать в консоле, писать небольшие классные однострочники. Опция -M подключает модуль и получается таким образом имя фреймворка -Mojo. Там собраны некоторые функции, в методе import, через вышеописанную функцию monkey_patch добавляются. И теперь, как в конце примера показано, можно получить заголовок любого сайта например, тут получаем заголовок официального сайта Mojolicious или заголовки постов на главной habrahabr.ru.
package ojo;
#
sub import {
#
monkey_patch $caller,
# ...
g => sub { _request($ua, 'GET',    @_) },
# ...
x => sub { Mojo::DOM->new(@_) };
# из консоли, заголовок сайта
perl -Mojo -E 'say g("mojolicio.us")->dom->at("title")->text'
# заголовки постов на главной habrahabr.ru
perl -Mojo -C -E 'g("habrahabr.ru")->dom->find("a.post_title")->each(sub{ say $_->text })'

Пример 24:
Вызов функции для каждого элемента коллекции, элемент доступен как первый аргумент функции либо через $_ тут, как и в примере выше со списком заголовков постов. Хочется подчеркнуть, особенность в том, что сделано так, чтобы каждый элемент был доступен через $_, это удобно. Точно так же как в стандартных функциях grep, map.
package Mojo::Collection;
#
sub each {
  my ($self, $cb) = @_;
  return @$self unless $cb;
  my $i = 1;
  $_->$cb($i++) for @$self;
  return $self;
}
#
$dom->find('p[id]')->each(sub { say $_->{id} });

Пример 25:
Создание рандомного уникального значения среди ключей хэша. Также создания хэша, если он еще не создан, и так же как в примере выше, присвоение простой локальной переменной, для того чтобы проще его использовать в блоке или функции.
my $timers = $self->{timers} //= {};
my $id;
do { $id = md5_sum('t' . steady_time . rand 999) } while $timers->{$id};

Пример 26:
Комментарий перед блоком else или else if.
}
# New socket
else {

Пример 27:
Использование функции shift в качестве ключа хэша, необходимо вызывать ее со скобками, иначе будет воспринята как строка «shift».
Также для напоминания, если кто забыл, в этом примере обращение сразу же к нескольким элементам хэша, удаление сразу же нескольких ключей.
sub reset { delete @{shift()}{qw(io poll timers)} }
#
sub get { (shift->{cache} || {})->{shift()} }

Пример 28:
Перегрузка операций для работы с данным объектом, fallback => 1 нужно, если перегруженная операция не определена, чтобы не выскакивала ошибка и чтобы был поиск подходящей операции.
package Mojo::Collection;
use Mojo::Base -strict;
use overload
  bool => sub { !!@{shift()} },
  '""' => sub { shift->join("\n") },
  fallback => 1;
# пример использования перегруженных операций
my $collection = Mojo::Collection->new(qw(just works));
say $collection;
say "empty" unless $collection;

Пример 29:
В блоке-условии for, возможно ссылки на хэш нету.
$self->_finish($_, 1) for keys %{$self->{connections} || {}};

Пример 30:
Перенос длинного выражения с присваиванием на другую строку.
my $stream
  = $self->_loop($c->{nb})->stream($id)->timeout($self->inactivity_timeout);

Пример 31:
Отличие return от return undef. Просто return вернут в контексте списка пустой список, в скалярном контексте вернет undef. В то время как return undef в любом контексте вернет undef.
return undef if $self->tree->[0] eq 'root';
#
return unless defined $lines->[0][$num - 1];
# свои примеры не верного использования
sub foo { return undef }
if (my @x = foo()) { print "oops, we think we got a result"; }
#
sub foo { return }
%x = ('foo' => foo(), 'bar' => 'baz');
if (!exists $x{'bar'}) { print "oops, bar became a value, not a key"; }

Пример 32:
Преобразование любого типа к булевому.
sub has_data { !!keys %{shift->input} }


Ну вот как-то так, самые интересные куски кода привел тут, если кому интересно, надо полистать на GitHub еще. И если свой код подводить к такому, то должно наверное получиться хорошее приложение.
Tags:
Hubs:
+17
Comments 25
Comments Comments 25

Articles