Perl code style

Материал из YourcmcWiki
Перейти к: навигация, поиск

Данная статья отражает моё мнение по поводу правильного стиля кода в Perl’е.

Оно не совпадает со стандартными рекомендациями perlstyle, потому что, ИМХО, оно лучше — приводит к гораздо более читаемому коду :)

Кстати, аналогичный стиль я всегда использую при программировании и на других языках — и на Java, и на C/C++, и на JavaScript, и на PHP. Не получается только с убогим питоном — Гвидо же выпендрился, убрав фигурные скобки совсем…

Отступы, пробелы

Желательно, чтобы все строки укладывались в 100—120 символов, но это не следует рассматривать как жёсткое требование: мониторы по 80 символов в ширину сейчас редкость, а случаи, когда длинные строки читаемее коротких, встречаются.

Размер отступа — строго 4 пробела либо 1 таб. Никаких смесей пробелов и табов — ни для «симуляции половины таба» (встречается стиль кода, где 1 отступ = 4 пробела, а 2 отступа = 1 таб — такой стиль запрещается), ни для выравнивания условий if () или параметров функций по открывающейся скобке. Если в файле пробелы, то только пробелы; если табы — то только табы.

Каждый уровень выравнивается строго на +1 отступ.

Например, продолжение выражения на новой строке всегда выравнивается ровно на +1 отступ. Именно на +1, а не на половину или ещё сколько-то:

$template->process('file.tmpl', $vars)
    || ThrowTemplateError($template->error);

Второй пример — если список параметров функции не помещается на 1 строчку — он переносится на следующую строчку с одинарным отступом, а закрывающая скобка помещается на отдельную строчку без отступа:

$self->example(
    'very long parameter', 'very long parameter', 'very long parameter', 'very long parameter'
);

Например, вот так НЕ ПИШЕМ НИКОГДА (отступы всегда должны расти ровно на 1 уровень!):

$self->example('very long parameter', 'very long parameter',
               'very long parameter', 'very long parameter');

Тело блока всегда глубже открывающих/закрывающих конструкций и скобок ровно на 1 отступ. Есть, например, такой вот странный вариант — так НЕ ПИШЕМ НИКОГДА:

if ($a)
    {
    ...
    }

При переносе условия if () на новую строчку логические операторы остаются в предыдущей строке:

if ($a && $b && $c &&
    $d && $e)
{
    ...
}

После ключевых слов for, if и подобных всегда следует пробел:

for (my $a = 0; $a < 100; $a++) ...

После открывающих и перед закрывающими скобками пробелы НЕ СТАВЯТСЯ (есть извращенцы, считающие, что это повышает читаемость, а по-моему — это ужас). То есть вот так не пишем НИКОГДА:

if ( $a == 1 ) ...

После имён функций в вызовах пробелы не ставятся:

function($arg1, $arg2);

Пробелами обрамляется большинство операторов — присваивания, сравнения, логические, арифметические и т. п. Исключение могут составлять слагаемые в арифметических выражениях, например, $a*10 + 5.

Файл всегда должен заканчивается переводом строки.

Блоки

Фигурные скобки ВСЕХ блоков, содержащих более 1 оператора, начинаются с новой строки:

sub x
{
    ...
    if (...)
    {
        ...
    }
    else
    {
        ...
    }
    ...
}

Если блок содержит только 1 оператор и этот оператор очень короткий, его можно разместить на 1 строке:

eval { &$sub() };

Конструкции map { … } и grep { … } желательно записывать в 1 строку. Если условие очень длинное — либо превратить в цикл for(), либо записать условие на отдельных строчках, оставив фигурную скобку на той же строке, что и grep:

grep {
    ... &&
    ... &&
    ...
} @array

Если блок содержит несколько операторов, можно даже перенести фигурную скобку { на новую строку:

map
{
    my $a = $_;
    $a =~ s/[abc]/a/g;
    $a;
} @array

Фигурные скобки хешей почти всегда начинаются на той же строке, что и предшествующий оператор:

my $hash = {
    ...
};

Если хеш очень длинный — не возбраняется перенести фигурную скобку на новую строчку.

В случае, если тело цикла — пустое, следует всегда явно добавлять пустой блок вместо пустого оператора «;»:

for ($a = 0; $s{$a}; $a++) {}

вместо

for ($a = 0; $s{$a}; $a++);

Perl

Общее правило: поменьше «синтаксического плюрализма», то есть всяких извращённых вариантов записи, в целом позволяемых Perl’ом. Лучше, чтобы все конструкции выглядели однозначно.

Всегда следует использовать

use strict;

Для строк не следует использовать синтаксис q{}, qq{}. Обычных кавычек ('одинарных' и "двойных") вполне достаточно. HEREDOC — разрешён, но на крайний случай, для очень длинных строк.

Суффиксная запись if/unless/for допускается только для очень коротких одинарных и однострочных операторов. Даже если тело if’а или for’а однострочное, но условие длинное и его приходится переносить на следующую строчку — лучше предпочесть обычную запись с фигурными скобками. Если тело выглядит слишком коротким — лучше заставить себя добавить к нему комментарий:

if (!($a = $self->_template_lang_directories(Bugzilla->languages, "extensions/BmpConvert")))
{
    # комментарий
    $self->_search_templates(Bugzilla->languages, "extensions/BmpConvert");
}

Вместо

$self->_search_templates(Bugzilla->languages, "extensions/BmpConvert")
    if !($a = $self->_template_lang_directories(Bugzilla->languages, "extensions/BmpConvert"));

Частный случай: НИ В КОЕМ СЛУЧАЕ не должны использоваться конструкции вида (во vsem.ru, помню, работал какой-то любитель такого):

do
{
    ...
} if (...);

Проверки на пустоту/непустоту следует делать БЕЗ дополнительных scalar(), keys(), == 0, > 0 и т. п. То есть, например, вместо:

if (scalar(@array) == 0) {}
if (scalar(keys(%$hash)) > 0) {}
if ($a == 0) {}

Следует писать:

if (!@array) {}
if (%$hash) {}
if (!$a) {}

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

keys %hash;
scalar @array;
push @array, "value";

В то же время, если результат выглядит неоднозначно — скобки лучше добавить. Например,

open(FD, "file") or die.

Вместо операторов and, or, not предпочтительно использовать C-подобные &&, ||, !. Даже если and/or требуются согласно приоритету операторов, то вполне возможно, что конструкцию лучше переписать так, чтобы можно было использовать «нормальные» операторы && или ||, так как это, вероятно, устранит её «визуальную» неоднозначность. Например, добавить скобки:

($a = $b) || return

вместо

$a = $b or return

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

Правило, типичное для всех языков: вместо большого числа позиционных параметров функции лучше передавать один хешреф.

Также предпочтительно НЕ использовать возврат списков и wantarray в функциях — лучше вернуть ссылку на массив или хеш.

Аргументы функции строго рекомендуется получать в её начале с помощью списочного присваивания, причём желательно с отдельной строкой my $self = shift — это помогает визуально отличать функции и методы:

sub f
{
    my $self = shift;
    my ($arg1, $arg2) = @_;
    ...
}

В принципе, также можно применять конструкцию

my ($self, $arg1, $arg2) = @_;

Другие конструкции (например, несколько shift подряд) допустимы, если это необходимо для удобства «разворачивания» остальных атрибутов функции.

Ключи хешей, которые можно записать БЕЗ кавычек, ВСЕГДА следует записывать БЕЗ кавычек. Кавычки следует добавлять только тогда, когда это действительно необходимо — то есть, если ключ содержит разделители (например, $hash{'a-b'}).

Обращение к вложенным ключам в ссылке на массив/хеш записывается через ->:

$hash->{key}->{key}->{key}

Обращение к вложенным ключам в списке или хеше (не в ссылку на массив/хеш) записывается без ->:

$hash{key}{key}{key}

Длинные SQL операторы следует записывать в несколько строк с конкатенацией и пробелом в начале продолжения каждой новой строки:

$dbh->do(
    "SELECT field1, field2, field3, field4" .
    " FROM table" .
    " INNER JOIN table2 ON table2.x=table.x" .
    " WHERE x=?",
    undef, $x
);

А не, например, вот так (помним, что отступы всегда растут ровно по 1 уровню):

$dbh->do("
        SELECT field1, field2, field3, field4
          FROM table
    INNER JOIN table2
            ON table2.x=table.x
         WHERE table.x=?
", undef, $x);

Программирование

Нежелательно пользоваться линейным поиском в массиве. Практически всегда лучше искать с помощью хеша.

DRY (!!!) Если блок кода повторяется хотя бы ДВА раза — повторение следует СРАЗУ убрать и вынести в функцию.

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

Если в проекте используется база данных, всегда должен существовать явный способ её инициализации либо специальными скриптами инициализации, либо просто SQL-файлом, в обоих случаях входящими в код проекта и версионируемыми вместе с ним. Иными словами, если новую базу данных легче всего инициализировать копированием с существующей (боевой или тестовой) системы — это беспредел. Также очень желательно, чтобы были и скрипты миграций (то есть, обновления схемы) БД — опять-таки хотя бы просто SQL-файлами.

В SQL-запросах следует ВСЕГДА использовать bind-переменные, и лишь только в крайнем случае — подстановку значений с помощью $dbh->quote().

Желательно использовать методы $dbh->select*** и помнить о {Slice=>{}}, дающем возможность получить строчки из БД в самом удобном виде — в виде ссылки на массив хешрефов, например,

my $rows = $dbh->selectall_arrayref("SELECT * FROM table WHERE x=?", {Slice=>{}}, $var1);

Для получения ссылки на массив со значениями первой колонки — $dbh->selectcol_arrayref; для получения первой строчки в виде списка — $dbh->selectrow_array; иногда можно применять и $dbh->selectall_hashref.

При этом нежелательно без крайней необходимости использовать пары prepare/execute, построчный fetch и длинные списки переменных вместо хешей:

my $sth = $dbh->prepare("SELECT ...");
$sth->execute($var);
while (my ($field1, $field2) = $sth->fetchrow_array)
{
    ...
}

Пары prepare/execute и построчный fetch имеет смысл использовать только при потоковой обработке очень больших наборов данных, и в этом случае при использовании MySQL обязательно включить mysql_use_result.