REG.RU coding standards

В данном документе описаны стандарты кодирования (Perl, SQL, HTML), используемые в проекте РЕГ.РУ. Предлагаемый список советов и требований, разумеется, не является исчерпывающим.

Помимо данного документа рекомендуется к прочтению следующая литература:

Оглавление

  1. Оформление кода
  2. Переменные и константы
  3. Функции
  4. Модули
  5. Регулярные выражения
  6. Безопасность и надёжность кода
  7. Лаконичность кода
  8. XP
  9. Общие замечания
  10. SQL, базы данных
  11. Шаблоны Template Toolkit
  12. HTML-код
  13. JavaScript

1. Оформление кода

1.1. Лесенка в 4 пробела
Обязательна «лесенка» с отступом в 4 пробела (half-tab). При этом запрещается в редакторе изменять размер отображаемой табуляции, например выставлять отображение табуляции в 4 пробела. Код, созданный Вами при таких настройках, будет некорректно отображаться в других редакторах с другими настройками.
1.2. Пробелы после запятых
После запятых и точек с запятой (если, конечно, они не расположены в конце строки) ставятся пробелы. Перед запятой и точкой с запятой пробелы не ставятся:
@a = (1, 2, 3);
for (my $i = 0; $i < $count; $i++) {  }
1.3. Пробелы вокруг знаков операций
Любые операторы / знаки операций (перечисленных в perlop, например "=", "==", "=>", "<", ">", "&&", "||" и т.п.) обязательно отделяются пробелами с обоих сторон

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

$a = $b * $c + $d * $e;
$a = $b * $c  +  $d * $e;
1.4. Пробелы после ключевых слов
После любых ключевых слов языка perl, а также имён функций / методов, обязательно следует пробел. Исключение составляет случай, когда за именем функции/метода следует открывающая круглая скобка, за которой следует пробел.
Примеры:
@foo = grep !/^#/, @bar;
@foo = mygrep( qr/^F/, @bar );
1.5. Пробелы вокруг сложных индексных выражений
В случае, если Вы обращаетесь к элементу массива или хэша по индексу и индексное выражение достаточно сложное, отделяйте его пробелами для улучшения удобочитаемости. Если выражение простое — пробелы не обязательны.
$a[1];
$a[ 1 + 2 + 2 + 4 * function( $a{ $b->{c} } ) ];
1.6. Пробелы внутри круглых скобок при вызове функций
При вызове функций с аргументами, если аргументов больше одного и аргументы сложны, для улучшения визуального восприятия аргументов, лучше сделать пробелы внутри круглых скобок:
do_something_simple('abc'); # В этом случае аргументы пробелами можно не отделять
do_something_comprehensive( $a[1], $b->{c}->{d}->[0]->doit(), 1, undef, 2 ); # Хорошо
do_something_comprehensive($a[1], $b->{c}->{d}->[0]->doit(), 1, undef, 2); # ПЛОХО!
1.7. Пробелы после знака комментария
После символа начала комментария («#») перед текстом самого комментария ставится пробел:
# Комментарии начинаются С ЗАГЛАВНОЙ БУКВЫ!
# Вторая строка комментария
Исключение составляют fancy comments, где допускается сливать начальнуй символ решётки с последующими символами:
############# MY COMMENT ###############
#************** INIT *******************
1.8. «Опять пробелы???»
Для того, чтобы понять, насколько хорошо отформатирован Ваш исходный текст: достаточно ли отступов, пробелов и пустых строк — попробуйте отключить подсветку синтаксиса в Вашем редакторе. Если после отключения подсветки код по-прежнему легко читаем (просмотр и анализ текста производится легко, любые конструкции легко выделяются визуально) — значит код действительно удобочитаем.

Не стоит полагаться на подсветку синтаксиса как на «костыль», скрывающий недостатки форматирования.

1.9. Выравнивайте комментарии точно так же, как и код
Левый край комментариев выравнивается точно так же, как и основной код, т.е. используется принцип "лесенки".
# Тили-тили
# Трали-вали
if ($cond) {
    # Это дело мне по силе,
    # Откажусь теперь едва ли.
}
else {
    # Это мы не проходили,
    # Это нам не задавали!
}
Ставить символы «решётки» вначале строки, если левая граница кода находится правее, не допускается.
# Тили-тили
# Трали-вали
if ($cond) {
# ТАК ДЕЛАТЬ НЕЛЬЗЯ!!!
}
1.10. Максимальная длина строк. Разбиение длинных строк.
Строки желательно не оставлять слишком длинными; условное ограничение — 80-100-120 символов в строке. При необходимости строка разбивается на несколько.
Примеры допустимого разбивания конструкций:
very_long_statement
    if condition;
if (
    very_long_condition_1
    && very_long_condition_2
) {
    statement;
}
if (
    ..
  &&
    ...
  ||
    ...
) {
    ...
}
1.11. Открывающая фигурная скобка на той же строке, что и ключевое слово
Старайтесь придерживаться компактного (K&R) стиля оформления циклов и блоков ветвления: открывающая фигурная скобка находится на той же строке, что и ключевое слово «for», «if», «else», «while» и т. п.

После открывающей фигурной скобки обязателен перевод строки. Т. е. содержимое блока начинается с новой строки. Из этого правила могут быть исключения, см. п. 1.14.

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

if ($condition) {
    statement1;
}
else {
    statement2;
}
for (my $i = 0; $i < $count; $i++) {
    statement;
}
1.12. else — на отдельной строке
Не ставьте ключевые слова else и elsif на той же строке, что и закрывающая фигурная скобка, заканчивающая предыдущий блок. Начинайте конструкцию else / elsif с новой строки!
if ($condition) {
    statement1;
}
else {
    statement2;
}

Обоснование см. в PBP стр. 24: «Don't cuddle an else».

1.13. Пробел перед открывающей фигурной скобкой
Перед открывающей фигурной скобкой в блочных конструкциях всегда ставится пробел:
for (@array) {
    statement;
}
1.14. Допускается компактное оформление блоков из одного оператора
Однострочные блоки, состоящие из единственного оператора, могут быть помещены в одну строку вместе с открывающими и закрывающими скобками:
for (@array) { $_ *= 2; }
Возможно, лучше в подобных случаях использовать постфиксную форму записи:
$_ *= 2 for (@array);
1.15. Ставьте точку с запятой после каждого оператора
В Perl символ «;» является всего лишь разделителем (а не терминатором) операторов. В результате синтаксис позволяет НЕ ставить точку с запятой после последнего оператора блока. Однако, ставьте точку с запятой всё равно, даже если у вас всего лишь один оператор в блоке.

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

push @a, $a
print @a # Ошибка! Пропущена ";"
1.16. Ставьте запятую после каждого значения в многострочном списке
Следуя этому правилу, Вы сможете избежать лишних ошибок при добавлении элементов в конец списка. Также Вам будет проще перегруппировывать элементы списков, не заботясь о расстановке запятых.
my @dwarves = (
    'Happy',
    'Sleepy',
    'Dopey',
    'Sneezy',
    'Grumpy',
    'Bashful',
    'Doc',
);
1.17. Избегайте лишней пунктуации
Избегайте излишней пунктуации. В особенности, лишних скобок при вызове функций, или при выделении условий.

Например, опускайте круглые скобки для задания аргументов встроенных функций и функций, имеющих прототип:

@foo = grep !/^#/, @bar;

или при написании условия в операторе if с постфиксной записью, а также в тернарных операторах:

print "Ok" if $ok1 && $ok2;

$message = $success ? "OK" : "NOT OK";

Только не забывайте о приоритетах операций.

Словом, стоит опустить лишние скобки там, где их отсутствие не идёт в разрез с удобочитаемостью.

1.18. Разбивайте код на абзацы, при необходимости снабжённые комментариями
Код внутри функций должен быть разделён на смысловые блоки, выполняющие определённую узкую задачу. Смысловые блоки отделяются друг от друга пустыми строками. Для дальнейшего улучшения сопровождабельности кода, добавляйте вначале каждого абзаца однострочный комментарий, объясняющий, что делает эта последовательность операторов.
sub addarray_internal {
    my ($var_name, $needs_quotemeta) = @_;

    # Запомнить оригинал...
    $raw .= $var_name;

    # Добавить экранирование спецсимволов, если необходимо...
    my $quotemeta = $needs_quotemeta ?  q{map {quotemeta $_} } : $EMPTY_STR;

    # Перевести элементы переменной в строку, соединяя их с помощью "|"...
    my $perl5pat = qq{(??{join q{|}, $quotemeta \@{$var_name}})};

    # Добавить отладочный код, если необходимо...
    my $type = $quotemeta ? 'literal' : 'pattern';
    debug_now("Adding $var_name (as $type)");
    add_debug_mesg("Trying $var_name (as $type)");
    return $perl5pat;
}
1.19. Выравнивайте сходные элементы кода по вертикали.
Выравнивайте сходные элементы по вертикали, особенно если они достаточно короткие чтоб поместиться в одну строку:
my %wm_conts_map = (
    first_name  => 'iname',
    last_name   => 'fname',
    email       => 'email',
);

$IDX = $ST_MTIME;
$IDX = $ST_ATIME       if $opt_u;
$IDX = $ST_CTIME       if $opt_c;
$IDX = $ST_SIZE        if $opt_s;

mkdir $tmpdir, 0700 or die "can't mkdir $tmpdir: $!";
chdir($tmpdir)      or die "can't chdir $tmpdir: $!";
mkdir 'tmp',   0777 or die "can't mkdir $tmpdir/tmp: $!";

2. Переменные и константы

2.1. Не отделяйте имена переменных и функций от следующей за ними открывающей скобки
Важно ставить открывающую скобку слитно с именем функции или переменной. В противном случае можно визуально спутать функцию с ключевым словом, а начало выражения для элемента массива или хэша со скаляром.
# ХОРОШО
next CANDIDATE if open_region($i);

$candidates[$i] = $incumbent{ $candidates[$i]{region} };
# ПЛОХО!
next CANDIDATE if open_region ($i);

$candidates[$i] = $incumbent { $candidates[$i]{region} };
2.2. Осмысленные названия идентификаторов
Выбирайте осмысленные названия для идентификаторов (переменных, констант, функций). Исключение составляют итераторы циклов, где допускаются короткие идентификаторы: $i, $n и т.п. При этом не допускается калька с русского языка («$polzovatel», «$sajt» и т.п.). Если вы не можете вспомнить, что это имя значит — у вас проблемы.
2.3. Строчные буквы для названий переменных и функций
Названия всех переменных и функций должны состоять только из строчных букв, цифр и знаков подчёркивания: «get_domain_name» и т.п.

Хотя короткие идентификаторы типа $gotit возможно и неплохи, используйте знак подчеркивания для разделения слов. В общем случае $var_names_like_this прочесть легче чем $VarNamesLikeThis.

2.4. Заглавные буквы для констант
Константы именуются только с использованием заглавных букв:
use constant DEBUG => 0;

Readonly my $STAGING => 1;
2.5. Именуйте массивы во множественном числе, а скаляры в единственном
Массивы рекомендуется называть во множественном числе: @users, @objects, а скаляры — в единственном: $user, $object. Что касается хешей, то некоторые авторы рекомендуют также именовать их в единственном числе (%param), т. к. для хэшей гораздо более распространено обращение к единственному их элементу ($param{user_id}), в то время как массивы чаще обрабатываются целиком (см. пп. 5.3, 5.4).
2.6. Используйте именованные итераторы в циклах for/foreach
Использование $_ в качестве итератора в циклах for / foreach допускается лишь в случае очень коротких, однострочных циклов / циклов с постфиксной записью:
print $_ for @array; # Допустимо

for (@array) { # НЕДОПУСТИМО!
    .... # длинное тело цикла
}
См. PBP стр. 105: «Use named lexicals as explicit for loop iterators».
2.7. Не используйте «магических чисел»
«Магическое число» — это число, которое появляется в коде без всякого объяснения; например «$bank_account_balance *= 57.492;». Вы смотрите на это число и не понимаете, откуда оно взялось и почему именно оно. Поскольку Вы не понимаете значения этого числа, Вы не понимаете код.

В целом, числовые литералы кроме 0 и 1 вообще не должны использоваться. Если Вам нужно использовать другие константы — оформите их как константы (через use constant, use Readonly или как ещё) с осмысленным именем.

use Readonly;
Readonly::Scalar $PI => 3.14159265358979;

...

my $square = $PI * $radius ** 2;
См. Perl::Critic::Policy::ValuesAndExpressions::ProhibitMagicNumbers.
2.8. Не используйте числовые коды ошибок. Используйте осмысленные строковые коды.
Это правило — следствие из предыдущего. Вместо числовых кодов ошибок используйте осмысленные строковые — так гораздо нагляднее. Сравните: код 3654 vs 'HOSTNAME_INVALID', 20053 vs 'NOT_ENOUGH_MONEY_ON_ACCOUNT'. Магическим числам в коде (в том числе в качестве кодов ошибок) — наше решительное нет!
2.9. use English
use English использовать не запрещается, но предпочтение отдается коротким именам. Они - стандарт мира Perl и знание коротких переменных упрощает чтение кода сторонних модулей.

3. Функции

3.1. Одна функция выполняет одну задачу
Если функция выполняет несколько разных, слабо связанных друг с другом задач, подумайте о том, чтобы разбить эту функцию на несколько.
3.2. Имена функций должны содержать глагол
Наименования функций должны включать глагол, например «get_domain_name», или «chash_my_program».

Имена, заданные без учёта этого принципа, вроде «flat_components», могут быть истолкованны совершенно по разному, например как «get_flat_components», «set_flat_components», «update_flat_components», «remove_flat_components» или «add_flat_components».

3.3. Используйте устоявшиеся пары антонимов в именах функций
В книге Стива Макконелла «Совершенный код» приводятся устоявшиеся пары антонимов, рекомендуемые для использования в именах функций/методов, а именно:
add / remove,
begin / end,
create / destroy,
first / last,
increment / decrement,
insert / delete,
lock / unlock,
min / max,
next / prev,
old / new,
open / close,
show / hide,
source / target,
start / stop,
up / down.

Использование «несогласованных» пар глаголов вроде add / delete или insert / destroy вводит в заблуждение и усложняет анализ кода.

3.4. Имена private-функций начинаются с подчёркивания
Если в модуле присутствуют функции, предназначенные только для внутреннего использования, которые никогда не будут вызваны за пределами модуля (за исключением случая автоматического тестирования), можно предварять имена этих private-функций знаком подчёркивания. Пример: _do_some_private_actions.
3.5. Отступы и комментарии для функций
Функции отделены друг от друга минимум одной пустой строкой. Для каждой функции необходимо краткое описание того, что она делает:
# Получить имя домена по его id
sub get_domain_name {
    …
}
или так,
=item B<get_domain_name>($id)

Получить имя домена по его id

=cut

sub get_domain_name {
    …
}
3.6. Приём входных параметров функций
Параметры принимаются с использованием конструкции:
my ($param1, $param2) = @_;

Она должна располагаться в первой строке функции и отделяться пустой строкой от последующего кода. Этот вариант считается стандартным и должен применяться всегда, кроме описанных ниже исключений.

Возможно использование shift и pop в сложных случаях, где это обоснованно (т. е. требуется изменение @_, либо используется хитрая последовательность параметров): некоторые случаи использования AUTOLOAD, работы с коллбэками и т. п.

В случае переменного количества параметров, если количество и смысл последующих параметров может варьироваться в зависимости от значения предыдущих параметров, тоже допускается принимать эти первые по счёту параметры с помощью shift:

my $action = shift;

my ($name, $value) = @_ if $action eq 'new';
my ($reason) = @_ if $action eq 'destroy';

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

my ( $action, $params ) = @_;

if ( $action eq 'new' ) {
    # работаем с $params->{name} и $params->{value}
}
elsif ( $action eq 'destroy') {
    # работаем с $params->{reason}
}

Допускается работа с аргументами функции напрямую через $_[0], когда это явно требуется с точки зрения логики работы функции или производительности, например:

  1. функция работает с большими кусками данных и нужно избежать их копирования из переданных параметров во внутренние переменные;
  2. функции требуется изменять переданные в неё аргументы (например, функции декодирования, рекурсивного обхода структур данных).

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

Именованные аргументы принимаются следующим образом:

my %param = @_;
3.7. Документирование входных параметров функций
Если аргументы функции нетривиальны (их назначение и набор допустимых значений не очевидны из их названия и контекста), после описания функции добавляется строка пояснения по аргументам. Если какой-либо аргумент может принимать конечное дискретное множество значений — это множество также перечисляется в комментариях. Если используются именованные параметры — все возможные параметры также перечисляются в комментариях, обязательные параметры обозначаются звёздочкой:
# %param: action*, domain_name*, comment
# action: new, renew, delete
3.8. Не более 3-х позиционных параметров у функций
Функция может принимать не более 2-х, максимум 3-х ПОЗИЦИОННЫХ аргументов. При большем количестве аргументов либо проводится рефакторинг с целью уменьшения количества входных параметров, либо используются именованные параметры:
my %param = @_;
Именованные параметры обязательно документируются (см. п. 3.7)
3.9. Проверка аргументов
В случае использования именованных параметров, если есть как обязательные, так и необязательные параметры, желательно проверять наличие обязательных параметров. В случае их отсутствия — вызывать исключение с помощью Carp::confess (для получения stack backtrace) или Carp::croak. В случае использования позиционных параметров проверка наличия и корректности переданных аргументов также приветствуется.
3.10. Документирование выходных параметров функций
Если выходные параметры функции не очевидны / нетривиальны, особенно это касается возврата сложных структур данных, желательно их документировать (после описания входных параметров).
3.11. Возврат скаляра или списка, в зависимости от wantarray
Если функция возвращает список значений, но из возвращаемого списка часто может использовать только первое значение, желательно выдавать только первое значение или весь список в зависимости от контекста:
return wantarray ? (user_id, username) : user_id;

4. Модули

4.1. Наименование модулей в стиле MyModuleName
Модули .pm (и, соответственно, файлы модулей) следует называть в стиле MyModuleName, т.е. слова склеиваются между собой, каждое слово записывается с заглавной буквы. В то время как исполняемые файлы ("скрипты"), следует именовать маленькими буквами, с применением знака подчёркивания, и использовать суффикс .pl: clear_cache.pl.
4.2. Комментарий или POD вначале модуля
Вначале модуля должен быть комментарий или кусок POD, описывающий назначение модуля.
4.3. После последней строки модуля обязателен перенос
Последняя строка кода модуля обязательно заканчивается переносом строки.
4.4. use Modern::Perl;
Обязательное включение всех предупреждений и максимальное ужесточение автоматического контроля:
use Modern::Perl;

## Ранее использовали
# use strict;
# use warnings;
4.5. Динамическая подгрузка тяжёлых опциональных модулей
Если требуется использовать "тяжёлую" библиотеку или модуль (скажем, отъедающую 5 Мб памяти и более), при этом, необходимость в этой библиотеке возникает очень редко, при определённых условиях — лучше загружать такой модуль динамически, когда в нём возникает необходимость:
if ($condition) {
    require Compress::Zlib;
    # import Compress::Zlib ();
}
4.6. Не стоит злоупотреблять экспортированием функций
Не стоит экспортировать функции направо и налево без серьёзной необходимости. Во-первых, в этом случае засоряется namespace модулей, а во-вторых голое название функции без квалификатора может ввести в заблуждение: не очевидно, в каком модуле функция определена — это увеличивает время на понимание чужого (да и своего) кода и время на отладку.

Используйте либо название функции с полным квалификатором:

MyModule::mysub()

либо используйте ОО-интерфейс в Ваших модулях:

my $obj = MyObject->new;
$obj->method();

Если уж Вы экспортируете какие-либо функции, лучше делать их экспорт опциональным (EXPORT_OK вместо EXPORT)

4.7. Не стоит злоупотреблять импортированием функций.
При импорте модуля обязательно указывайте список используемых символов из него. Это упрощает определение списка зависимостей, и дальнейший рефакторинг кода. Кроме того, если модуль по умолчанию экспортирует много символов, скорость его импорта падает где-то на треть.
use POSIX qw( floor ceil ); # Правильно
use POSIX; # НЕПРАВИЛЬНО
4.8 Придерживайтесь синтаксиса прямого обращения к объектам.

Используйте оператор "стрелка" для обращения к объекту или классу.

        Class->class_method;
        my $obj = Class->new();
        $obj->method;
    

Косвенное обращение к классу или объекту может привести к запутыванию инерпретатора Perl.
Такое написание нежелательно:

        my $obj = new Class $value1, $value2;
        method $obj;
    

5. Регулярные выражения

5.1. Используйте флаг "x" для сложных регулярных выражений
Если вы используете действительно сложное регулярное выражение, используйте модификатор "x" и разделите текст пробелами, чтоб он не выглядел как нагромождение мусора.
5.2. Используйте удобные ограничители для регулярных выражений
Не используйте наклонную черту в качестве ограничителя когда ваше выражение содержит прямые или обратные наклонные черты:
# Используем удобный ограничитель
$s =~ s{/}{::};
# Это куда нагляднее, чем /\//::/;
5.3. Разбивайте сложные регулярные выражения на части
Будет гораздо нагляднее, если Вы составите сложное регулярное выражение из нескольких кусочков. Кусочки можно хранить как в виде строк, так и в виде qr//:
# Строим регексп для матчинга чисел с плавающей точкой...
Readonly my $DIGITS    => qr{ \d+ (?: [.] \d*)? | [.] \d+         }xms;
Readonly my $SIGN      => qr{ [+-]                                }xms;
Readonly my $EXPONENT  => qr{ [Ee] $SIGN? \d+                     }xms;
Readonly my $NUMBER    => qr{ ( ($SIGN?) ($DIGITS) ($EXPONENT?) ) }xms;

Подробности см. в PBP стр. 261: «Build complex regular expressions from simpler pieces.».

5.4. Безопасно обрабатывайте данные, получаемые извне
Если Вы используете данные, полученные извне, как есть, тем самым Вы подвергаете систему уязвимости. Используйте квотирование, например, так:
$test =~ m/\b\Q$param_name\E: \s+ (\d+)/x;

6. Безопасность и надёжность кода

6.1. Осторожное использование вызовов внешних команд
Прямое использование внешних вызовов команд (через "system" или "``") допускается только в особых случаях, при этом, если командная строка формируется внутри кода, необходимо быть абсолютно уверенным, что в командную строку не могут попасть входные переменные и параметры HTTP-запроса (см. п. 4.2).

Кроме того, НЕ СЛЕДУЕТ использовать qx и system в коде контроллеров. Эти вызовы могут быть сосредоточены только в коде ядра системы и соответствующие функции обязательно должны быть покрыты тестами (проверяющими невозможность использования эксплойтов).

6.2. Всегда проверяйте коды возврата системных вызовов.
Всегда проверяйте коды возврата системных вызовов. Если ошибка фатальна — вызывайте исключение с помощью die, Carp::confess или Carp::croak, передавая соответствующее сообщение об ошибке. Хорошее сообщение об ошибке должно включать: в каком месте программы возникла проблема, какой системный вызов был произведен, с какими аргументами, и (ОЧЕНЬ ВАЖНО) должно содержать стандартное системное описание ошибки.
Пример:
open( my $fh, ">", $filename ) or die "Can't open $filename for writing: $!";
print   $fh '....';
6.3. Обработка исключений
При реализации обработки исключений, лучше использовать:
        eval {
            ...
            1;
        } or do {
            ...
        };
    
смысл eval-do в том что блок обработки ошибок (в "do") должен следовать непосредственно после eval, чтобы между ними нельзя было вставить какой-то другой код (который может испортить $@), так же решение о том, было ли исключение или нет, должно приниматься на основе кода возврата eval{} а не на основе переменной $@. если в данном конкретном eval-do не удобен, допустимы другие конструкции, основанные на том же принципе (однако для консистентности лучше попытаться всё же использовать eval-do):
        if (eval { ...; 1 }) {
            ...
        }
        else {
            ...
        }

        ok ! eval { ...; 1 }, "some test";
    
если в блоке обработке ошибок мы не анализируем $@, отступление от этого правила (в части $@) допустимо:
    my $success = eval {
        ...
        1;
    };

    ...

    if ($success) {
        ...
    }
    else {
        ...
    }
    
если речь идёт не о паттерне программирования "обработка исключений" (а о каком-то системном программировании, о eval EXPR), то допускается отходить от этого правила, если это сделает код лучше.

Безопасность кода при работе с БД

6.3. Недопущение возможностей для SQL-inject:
Подстановка данных в SQL-запросы ТОЛЬКО через placeholders ("?").
6.4. Минимизация изменений тела запроса
В особых случаях (если необходимо подставить имя поля или название таблицы) допускается подстановка переменных напрямую в тело запроса, при этом должны быть соблюдены два условия: Пример:
SELECT `$fieldname` FROM `$table`
Для кросс-dbms приложений можно использовать DBI-метод quote_identifier:
my $quoted_fieldname = $dbh->quote_identifier( 'fieldname' );

my $sql = "SELECT $quoted_name FROM mytable";

7. Лаконичность кода

7.1. Сокращённая форма записи, основанная на ||
Если это не ухудшает читабельность кода, приветствуется использование сокращённой формы записи, основанной на применении логического оператора «или». Например:
$a = $b || $c
лучше, чем
$a = $b ? $b : $c
и тем более лучше, чем
if ($b) {
    $a = $b;
}
else {
    $a = $c;
}
Также
$a ||= $b
короче и элегантнее, чем
unless ($a) {
    $a = $b;
}
7.2. Сокращённая форма записи, основанная на &&
Если это не ухудшает читабельность кода, приветствуется использование сокращённой формы записи, основанной на применении логического оператора «и».
$result = $switch && $value;
Данная конструкция присваевает значение $value переменной $result только в том случае, если $switch имеет true-значение. В противном случае будет присвоено значение $switch.

А в последующих случаях более уместно использование постфиксного if, нежели логической конструкции, основанной на &&.

# Метод method будет вызван только тогда,когда объект $obj проинициализирован.
$result = $obj && $obj->method;
# Но лучше постфиксное условие:
$result = $obj->method if $obj;
# Функция do_something будет вызвана только
# при выполнении условий $condition1 и $condition2
$condition1 && $condition2 and do_something();
# Но лучше использовать постфиксный if,
# т. к. Сразу бросается в глаза действие, а условие чаще всего вторично:
do_something() if $condition1 && $condition2;

7.3. Используйте and и or вместо условных конструкций if при обработке фатальных ошибок
В ряде случаях (когда при каком либо условии нужно выполнить всего один оператор) удобнее выполнять условный код с помощью and или or, чем громоздить блоки if:
mkdir $tmpdir, 0700 or die "can't mkdir $tmpdir: $!";

$username = get_user_name() and print "Username: $username\n";
7.4. Минимизация использования циклов for и обращений к элементам по индексу
Старайтесь избегать использование классического цикла for с целочисленным индексом, везде, где это возможно. В целом, старайтесь минимизировать обращение к элементам массивов по индексам. При обработке элементов списка отдавайте предпочтение циклам foreach, а также функциям, обрабатывающим сразу весь список сразу, таким как grep, map, sort, join, split и т.д.:
@foo = grep !/^P/, @bar;

@foo = map { $_ * 10 } @bar;

$_ *= 10 for @bar;

При этом не переусердствуйте: не стоит использовать map в void-контексте, там где уместнее было бы использовать for.


Обычно требуется обработка всех элементов массива сразу, а в этом случае удобнее использовать циклы foreach или функции, обрабатывающие массив целиком (см. п 5.4.). В случае использования foreach, grep, map элементы массива при каждой итерации предстают в виде переменной "$_" (или, для циклов foreach, любой другой переменной, какой Вы укажете), с которой работать куда удобнее, чем каждый раз обращаться к элементам массива по индексу.

Кроме того, часто необходим доступ только к крайним элементам массива (например, идёт процесс поэлементного заполнения массива и на каждой итерации мы добавляем новый элемент). В этом случае рекомендуется пользоваться функциями push, pop, shift, unshift. Применение этих функций позволяет получить более простой, компактный и БЫСТРЫЙ код. Сравните:

$cnt = 0;
while (condition) {
    ...
    $items[ $cnt++ ] = $value; # Калька с низкоуровневых языков
}

while (condition) {
    ...
    push @items, $value; # Короче и быстрее
}
7.5. Использование лаконичных вариантов функций DBI
Вместо последовательности DBI вызовов prepare / execute для изменения данных предпочтительнее использовать единственный вызов do.
Вместо последовательности DBI вызовов prepare / execute / fetch_* для выборки данных предпочтительнее использовать единственный вызов selectrow_* / selectall_* / selectcol_*.

8. XP

8.1. Тесты для всех функций / методов
Для каждой новой функции или метода должен быть написан автоматический тест. Лучше — несколько тестов, для проверки реакции на некорректные входные данные. Если Вы вносите серьёзные изменения в функцию и для неё не написаны тесты — создайте их.

Если какие-то чужие тесты у Вас не проходят, то это повод либо поправить исходный код (свой или чужой — не важно), либо тесты, если они не актуальны.

При создании тестов необходимо придерживаться следующих правил:

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

    Исключение составляют записи в логах — логи откатывать не нужно.

  2. Любые действия, производимые в процессе тестирования, меняющие состояние каких либо систем не должны покидать границ Вашей локальной машины. Т.е. запуск тестов у Вас не должен приводить к каким-либо изменениям во «внешнем мире» (логгирование Ваших операций чтения внешними системами — не в счёт). Например, исходящие письма, вместо отправки их реальным пользователям на реальные адреса в Интернете, должны отправляться на какой-либо тестовый ящик на локальной машине.

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

8.2. Рефакторинг
Если код Вам перестаёт нравится: функция слишком сложная и длинная (более 100 строк), слишком много входных и выходных параметров, неудачное название функции и т.п. — делайте рефакторинг. После проведения рефакторинга обязательно запускайте автоматические тесты. Если для функции, над которой Вы работали, тесты не предусмотрены -- создайте их.

9. Общие замечания

9.1. Don't Reinvent Wheel — Не переизобретайте колесо
Не переизобретайте колесо — если подобное колесо уже существует, просто адаптируйте его для своих нужд. Вероятно, для решения данной проблемы уже существует стандартный модуль в дистрибутиве Perl, или модуль из CPAN, или модуль, разработанный другими людьми внутри организации.
9.2. Do not Repeat Youself — не повторяй самого себя
Подумайте о повторном использовании кода. Зачем тратить впустую усилия мысли на одноразовые программы, если в будущем вам может потребоваться что-то подобное снова? Подумайте над обобщением вашего кода. Подумайте над написанием модуля или класса. Позаботьтесь о том, чтоб ваш код выполнялся без предупреждений с "use strict" и "use warnings" (или -w). Подумайте, не предложить ли Ваш код другим.
9.3. KISS — Keep It Simple, Stupid
Всегда и везде стОит придерживаться максимально простых решений, как в области архитектуры, так и в области непосредственно кодирования.

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

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

Из книги «Программист-прагматик. Путь от подмастерья к мастеру».

9.5. Выкладывайте все модули общего назначения в общий доступ
Если создаваемый Вами модуль или группа модулей жёстко не привязаны к разрабатываемой системе, а являются модулями «общего назначения», которые могут быть интересны другим разработчикам, лучше выделять такие модули в отдельные дистрибутивы и выкладывать на CPAN, в корпоративный репозиторий opensource-разработок или хотя бы на собственные домашние странички.

Зачем?

  1. Система и основной репозиторий «не захламляются» тем самым модулями общего назначения. В результате в основной системе легче ориентироваться
  2. Подготовка модулей к выкладке в общий доступ неизбежно повышает их качество (как минимум надо создавать документацию и тесты, «выпрямлять» код)
  3. Получаем все преимущества OpenSource (другие люди присылают багрепорты и патчи, способствуя улучшению модулей)
  4. Мы должны не только брать, но и отдавать что-то OpenSource-сообществу, возвращая тем самым свой долг
  5. Приятно раскручивать своё имя в CPAN ;)
9.6. Не исправляйте чужой код без причин

Нельзя исправлять чужой код, если на это нет вообще никаких причин (кроме личных представлений исправляющего, не зафиксированных в coding standards).

В том числе не нужно исправлять пробелы и выравнивания, если код и так нормально смотрится (субъективно) и не нарушают coding standards (объективно). Не нужно исправлять код если и до и после исправления он работает абсолютно одинаково и исправление не улучшило ничего - ни архитектуру/API, ни реализацию (исправление багов, уместная оптимизация, удаление ненужного кода), ни соответствие стандартам.

Исключение составляют ситуации, когда Вы вносите большие исправления в этот участок кода в рамках какой-либо вашей задачи.

9.7. Использование exists и defined (defined-or)

В булевом контексте не стоит использовать функции exists и defined (и аналогичные конструкции с оператором defined-or: "//", "//="), если можно обойтись без них.

  1. Если элемент хэша по логике может либо отсутствовать, либо быть булевым TRUE, то не стоит применять к нему функции defined и exists.
  2. Если элемент хэша по логике может либо отсутствовать, либо быть TRUE, либо быть FALSE, то не применять к нему функцию exists.

Цель - не стоит "делить" элементы на "not exists", "defined", "FALSE", "TRUE", когда можно обойтись только "TRUE" и "FALSE".

9.8. Не используйте экспериментальные возможности Perl

Не используйте given, when, smartmatch (~~) и другие экспериментальные возможности, по крайней мере до тех пор пока их судьба в последующих версиях Perl остаётся неясной.

10. SQL, базы данных

10.1. Форматирование SQL-запросов
Запросы длиной более 50 символов (примерно) рекомендуется разбивать на несколько строк, подгоняя текст по горизонтали. Например так:
SELECT fields
  FROM tables t
 WHERE conditions
   AND more conditions
 GROUP BY fields
 ORDER BY fields
 LIMIT limits
или так:
SELECT
    fields
FROM
    tables t
WHERE
    conditions
AND
    more conditions
GROUP BY
    fields
ORDER BY
    fields
LIMIT
    limits
Для оформления строки с SQL-кодом используются кавычки q// или qq//. Как и в случае обычных одинарных и двойных кавычек, если интерполяция внутри строки не нужна (т. е. в случае SQL почти всегда), то лучше использовать q//.
Примеры:
$dbh->do(q/
    SQL
/);

$dbh->do(q/
    SQL
    /, undef, $value
);

$dbh->do(q/
    SQL
    /,
    undef, $value
);

$dbh->do(q/
    SQL
    /,
    undef,
    $value,
    ...
    $value10
);
    
Запрещается использовать HEREDOC для оформления многострочного SQL, поскольку такой подход приводит к перестановке местами частей кода и ухудшает читаемость.
# Неправильно!!!
dbh_ro->selectrow_array( <<THEEND, undef, $service_id );
    SELECT state FROM services WHERE service_id=?;
THEEND
    
10.2. Ключевые слова — заглавными буквами
Все ключевые слова SQL записываются заглавными буквами, все наименование таблиц, полей, пользовательских функций — строчными.
10.3. Названия полей и таблиц — строчными буквами
При именовании таблиц и полей не допускается использование заглавных букв. Допускаются только строчные латинские буквы, цифры и знак подчёркивания. Пример: "name_of_the_table".
10.4. Название таблицы должно точно отражать вид объектов, которые в ней хранятся
Хорошее правило для того, как придумывать названия таблиц БД (но то же правило относится к названию массивов / списков)...
Таблица состоит из записей, запись в таблице почти всегда (если только это не таблица для связи многие-ко многим) отражает какой-то объект реального (или виртуального) мира. Назовите этот объект по-английски: точно, чётко и исчерпывающе, дабы не допускать разночтений и затем переведите во множественное число.

Удачные примеры: «users», «domains«, «domain_shop_categories», «user_doc_attachments».

Неудачные примеры: «statistics» — понятно, что имеет отношение к статистике, но неясно что это за статистика и объекты какого вида хранятся в таблице;
«queue» — понятно, что это какая-то очередь, но что именно за очередь неясно и объекты какого вида хранятся в таблице.

10.5. Первичный ключ с именем "id" или "<tablename>_id" *
В каждой таблице (за исключением таблиц, где необходимы составные ключи, например таблиц для обеспечения связей многие-ко-многим) обязательно должно быть поле с именем "id" (или "<tablename>_id") и типом INT, для которого должен быть создан первичный ключ:
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
Поле для первичного ключа может быть названо по другому и иметь другой тип, но это обязательно обсуждается с другими членами команды и выносится соответствующее обоснование.
10.6. Ссылочные поля — с именем "<tablename>_id" *
Все поля, использующиеся для связи с другими таблицами по их первичному ключу, именуются как "<tablename>_id", где "<tablename>" — имя таблицы в единственном числе. Примеры наименований: domain_id, user_id.
10.7. В командах INSERT / REPLACE всегда перечисляйте имена полей таблицы
В командах INSERT / REPLACE всегда перечисляйте имена полей таблицы:
REPLACE INTO wf_parking (hostname, uri_path, html, fwdto) VALUES (?, ?, ?, ?) -- ПРАВИЛЬНО
Никогда не делайте так:
REPLACE INTO wf_parking VALUES (?, ?, ?, ?) -- НЕПРАВИЛЬНО! Не перечислен список полей
Если имена полей явно не перечислять, то при любом изменении состава полей таблицы, например, при добавлении нового поля — неминуемо получим ошибку («column count doesn't match value count» и т. п.).

11. Шаблоны Template Toolkit

11.1. Экранирование поставляемых данных во избежание XSS-атак
При подстановке в шаблон данных, передаваемых пользователем (например, переданных ранее через форму на сайте), эти данные обязательно экранируются с помощью фильтра html:
[% company_name | html %].
Во избежание атак XSS.
11.2. Пробелы
К оформлению кода внутри шаблонов применяются те же самые требования, что и к оформлению кода на Perl. В частности это касается расстановки пробелов (пробелы вокруг операторов, пробелы после запятых, вокруг сложных индексных выражений и т.п.) Например,
[% news = [ { date => '31.12.2007', title => 'Happy new Year' } ] %]
[% FOREACH n = news %]
11.3. Лесенка
При использовании логических / блочных конструкций TT данные, внутри блока сдвигаются вправо для улучшения удобочитаемости:
[% IF ru %]
  русский текст
[% ELSE %]
  english text
[% END %]
11.4. Проверки на непустоту списков
В случае необходимости отображения списка или таблицы, где строки передаются в виде массива, должна быть проверка на непустоту этого массива и соответствующая адекватная реакция. Например, вместо вывода пустой таблицы или списка лучше вывести пояснительный текст: "Заказы пока отсутствуют" или "Новостей на сегодня нет".
11.5. Шаблон должен интерпретироваться без ошибок, даже если отсутствуют необходимые данные
Шаблон должен интерпретироваться без ошибок даже в случае, если требуемые шаблоном переменные не передаются шаблонизатору. Это означает, что в шаблонах должна быть предусмотрена адекватная защита, на случае, если данные не передаются.

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

11.6. Запрещается использовать имена, которые имеют предопределенное значение
В шаблонах Template Toolkit/Catalyst предопределены имена c, context, template и component.

Кроме того, наша система лэйаутов резервирует для себя имена current_layout, html и content.

Запрещается передавать в контекст шаблонов переменные/методы/etc. с такими именами.

12. HTML-код

12.1. HTML-код должен соответствовать спецификации HTML 5 / XHTML.
HTML-код по-возможности должен соответствовать спецификации HTML 5 и XHTML, а это означает, что:
12.2. Тире всегда записывается как &mdash;
Тире всегда записывается как &mdash;. Использование дефиса вместо тире не допускается. Более подробно тема раскрыта в параграфе Арт. Лебедева «Тире, минус и дефис, или Черты русской типографики».
12.3. Кавычки-«ёлочки» вместо "английских двойных".
В HTML-документах, создаваемых для русскоязычной аудитории, желательно использовать кавычки-«ёлочки» (символы &laquo; и &raquo;). В документах, создаваемых для внутреннего использования (внутренняя документация, технические задания, должностные инструкции и т.п.) допускается использование "английских двойных" кавычек.
12.4. Не использовать спецсимволы, записанные в национальной кодировке
Не использовать знаки номера, копирайта, спец-кавычек и т.п., записанные в виде символов в национальной кодировке, скажем cp1251. Далеко не во всех редакторах и операционных системах Ваши символы будут корректно отображаться. Для вставки подобных знаков используются html-entities:

Краткий справочник:

&copy;©знак охраны авторского права (copyright)
&reg;®знак зарегистрированных прав
&trade;символ зарегистрированного товарного знака
&laquo;«левая кавычка (левая ёлочка)
&raquo;»правая кавычка (правая ёлочка)
&mdash;тире
&ndash;короткое тире для обозначения интервалов
&minus;минус
&plusmn;±плюс/минус
&deg;°знак градуса
&lt;<знак «меньше»
&gt;>знак «больше»
&le;знак «меньше или равно»
&ge;знак «больше или равно»
&amp;&амперсанд
&nbsp; «несжимаемый» пробел
&sup1;¹1 в верхнем индексе
&sup2;²2 в верхнем индексе
&sup3;³3 в верхнем индексе
&#8470;знак номера
&cent;¢знак цента
&pound;£знак фунта
&yen;¥знак йены
&euro;знак евро
&sect;§знак параграфа
&middot;·точка (знак умножения)
&frac14;¼одна четверть
&frac12;½одна вторая
&frac34;¾три четверти
&bull;буллет
&hellip;многоточие
&lsquo; &rsquo;‘ ’одинарные лапки
&bdquo;открывающая лапка
&ldquo;закрывающая лапка
&rdquo;закрывающая английская лапка
&#9650;стрелки
&#9651;-- " --
&#9652;-- " --
&#9653;-- " --
&#9654;-- " --
&#9655;-- " --
&#9656;-- " --
&#9657;-- " --
&#9658;-- " --
&#9659;-- " --
&#9660;-- " --
&#9661;-- " --
&#9662;-- " --
&#9663;-- " --
&#9664;-- " --
&#9665;-- " --
&#9666;-- " --
&#9667;-- " --
&#9668;-- " --
&#9669;-- " --
12.5. Для каждого чекбокса и радиокнопки должен быть label
Для каждого элемента <input type="checkbox" ...> и <input type="radio" ...> должен быть предусмотрен элемент <label>, для того, чтобы чекбокс / радиобаттон срабатывал также по клику по метке:
<label><input type="checkbox" name="test" value="1" />Чекбокс 1</label>
<label><input type="radio" name="test" value="1" />Кнопка 1</label>
12.6. Грамотно используйте табличную форму представления информации
Вначале немного терминологии, касающейся таблиц. Содержимое таблицы организуется в колонки (графы). Таблица состоит из следующих основных элементов: нумерационного и тематического заголовков (номер таблицы и её название), головки (заголовочная часть таблицы), хвоста (вся остальная часть таблицы без головки), боковика (первая слева графа таблицы) и прографки (хвостовая часть таблицы без боковика).

Заполняя таблицу текстовыми или цифровыми данными полезно следовать правилам:

Взято отсюда.

12.7. Не загружайте незащищённое содержимое на страницах, отдаваемых по HTTPS
При посещении HTTPS-страниц, ссылающихся на «незащищённое» содержимое (загружающих JavaScript, картинки или стили, отдаваемые по протоколу HTTP) большинство браузеров генерирует предупреждение («Страница содержит небезопасное содержимое» и т. п.). Дабы этого избежать, избегайте комбинации «src="http:» на страницах, которые могут отдаваться по протоколу HTTPS.

Если сайт, на содержимое которого мы ссылаемся, поддерживает подключение как по HTTP, так и по HTTPS, можно написать так:

//sitename/path

Если же удалённый сервер, на содержимое которого мы ссылаемся, не умеет отдавать содержимое по HTTPS, то вообще не включаем это содержимое на страницах, отдаваемых через HTTPS:

[% UNLESS is_https %]
код, ссылающийся на http
[% END %]
12.8. «Лесенка» для HTML-кода
Используйте отступы для выделения иерархии элементов HTML:
<form action="..." method="post">
  <div>
    <h3>Заголовок</h3>
    ...
  </div>
</form>
Размер отступа не регламентируется, однако он должен быть отличен от нуля ;).
12.9. HTML-код должен проходить проверку w3c-валидатором
Используйте HTML-валидатор от w3c для проверки HTML-кода.

Для облегчения тестирования локальной версии сайта, дабы не выкладывать её в интернет для проверки, установите валидатор локально.

13. JavaScript

13.1. Всегда объявляйте переменные
Всегда объявляйте переменные в JavaScript, даже если Ваш браузер допускает использование переменной без её объявления. Не позволяйте себе «расслабиться»!
var variable = 'Value';
alert( variable );
13.2. Относитесь к JavaScript также трепетно, как и к Perl-коду
Все требования, сформулированные для Perl (пробелы, отступы, именование функций и переменных и т. п.), применимы также для JavaScript. Не следует считать его кодом «второго сорта», который «всё стерпит». «Сэкономленное» на написание небрежного кода время, потом будет потрачено в куда большем объёме на разбор и модификацию этого кода.

Буду рад услышать любые замечания и дополнения по данному документу. Пишите на адрес: despair {at} cpan {dot} org.

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

Постоянный адрес этой страницы в интернете: http://www.reg.ru/coding_standards.

© Walery Studennikov <despair [at] cpan {dot} org>