В данном документе описаны стандарты кодирования (Perl, SQL, HTML), используемые в проекте РЕГ.РУ. Предлагаемый список советов и требований, разумеется, не является исчерпывающим.
Помимо данного документа рекомендуется к прочтению следующая литература:
@a = (1, 2, 3);
for (my $i = 0; $i < $count; $i++) { };
В арифметических выражениях количество пробелов вокруг знаков операций можно варьировать, чтобы подчеркнуть приоритет операций. Примеры:
$a = $b * $c + $d * $e; $a = $b * $c + $d * $e;
@foo = grep !/^#/, @bar; @foo = mygrep( qr/^#/, @bar )
$a[1];
$a[ 1 + 2 + 2 + 4 * function( $a{ $b->{c} } ) ];
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); # ПЛОХО!
# Комментарии начинаются С ЗАГЛАВНОЙ БУКВЫ! # Вторая строка комментарияИсключение составляют fancy comments, где допускается сливать начальнуй символ решётки с последующими символами:
############# MY COMMENT ############### #************** INIT *******************
Не стоит полагаться на подсветку синтаксиса как на «костыль», скрывающий недостатки форматирования.
# Тили-тили
# Трали-вали
if ($cond) {
# Это дело мне по силе,
# Откажусь теперь едва ли.
}
else {
# Это мы не проходили,
# Это нам не задавали!
}
Ставить символы «решётки» вначале строки, если левая граница
кода находится правее, не допускается.
# Тили-тили
# Трали-вали
if ($cond) {
# ТАК ДЕЛАТЬ НЕЛЬЗЯ!!!
}
very_long_statement
if condition;
if (
very_long_condition_1
&& very_long_condition_2
) {
statement;
}
if (
..
&&
...
||
...
) {
...
}
После открывающей фигурной скобри обязателен перевод строки. Т. е. содержимое блока начинается с новой строки. Из этого правила могут быть исключения, см. п. 1.14.
Закрывающая фигурная скобка блока, состоящего из нескольких строк,
должна находиться на одной вертикали
с ключевым словом начинающим конструкцию.
Примеры:
if ($condition) {
statement1;
}
else {
statement2;
}
for (my $i = 0; $i < $count; $i++) {
statement;
}
if ($condition) {
statement1;
}
else {
statement2;
}
Обоснование см. в PBP стр. 24: «Don't cuddle an else».
for (@array) {
statement;
}
foreach (@array) { $_ *= 2; }
Возможно, лучше в подобных случаях использовать постфиксную форму записи:
$_ *= 2 foreach (@array);
Если точку с запятой не ставить, это может служить источником ошибок после добавления новых операторов в конец блока, т. к. на отсутствие точки с запятой после последнего оператора можно просто не обратить внимания. В результате получится неверный код:
push @a, $a print @a # Ошибка! Пропущена ";"
my @dwarves = (
'Happy',
'Sleepy',
'Dopey',
'Sneezy',
'Grumpy',
'Bashful',
'Doc',
);
Например, опускайте круглые скобки для задания аргументов встроенных функций и функций, имеющих прототип:
@foo = grep !/^#/, @bar;
или при написании условия в операторе if с постфиксной записью, а также в тринарных операторах:
print "Ok" if $ok1 && $ok2; $message = $success ? "OK" : "NOT OK";
Только не забывайте о приоритетах операций.
Словом, стоит опустить лишние скобки там, где их отсутствие не идёт в разрез с удобочитаемостью.
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;
}
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: $!";
# ХОРОШО
next CANDIDATE if open_region($i);
$candidates[$i] = $incumbent{ $candidates[$i]{region} };
# ПЛОХО!
next CANDIDATE if open_region ($i);
$candidates[$i] = $incumbent { $candidates[$i]{region} };
Хотя короткие идентификаторы типа $gotit возможно и неплохи, используйте знак подчеркивания для разделения слов. В общем случае $var_names_like_this прочесть легче чем $VarNamesLikeThis.
use constant DEBUG => 0;
print $_ foreach @array; # Допустимо
foreach (@array) { # НЕДОПУСТИМО!
.... # длинное тело цикла
}
См. PBP стр. 105: «Use named lexicals as explicit for loop iterators».
В целом, числовые литералы кроме 0 и 1 вообще не должны использоваться. Если Вам нужно использовать другие константы — оформите их как константы (через use constant, use Readonly или как ещё) с осмысленным именем.
use Readonly; Readonly::Scalar $PI => 3.14159265358979; ... my $square = $PI * $radius ** 2;См. Perl::Critic::Policy::ValuesAndExpressions::ProhibitMagicNumbers.
Имена, заданные без учёта этого принципа, вроде «flat_components», могут быть истолкованны совершенно по разному, например как «get_flat_components», «set_flat_components», «update_flat_components», «remove_flat_components» или «add_flat_components».
Использование «несогласованных» пар глаголов вроде add / delete или insert / destroy вводит в заблуждение и усложняет анализ кода.
# Получить имя домена по его id
sub get_domain_name {
...
}
my ($param1, $param2) = @_;
В случае переменного количества параметров, если количество и смысл последующих параметров может варьироваться в зависимости от значения предыдущих параметров, допускается принимать эти первые по счёту параметры с помощью shift:
my $action = shift; my ($name, $value) = @_ if $action eq 'new'; my ($reason) = @_ if $action eq 'destroy';
Именованные аргументы принимаются следующим образом:
my %param = @_;
После строки, принимающей аргументы, оставляется пустая строка:
my ($param1, $param2) = @_; # Последующий код
В случае очень коротких функций, там где производительность важнее удобочитаемости, для доступа к параметрам можно использовать $_[0], $_[1] и т. д.
После строки, с оператором, принимающим входные параметры, обязателен отступ в виде пустой строки.
# %param: action*, domain_name*, comment # action: new, renew, delete
my %param = @_;Именованные параметры обязательно документируются (см. п. 3.7)
return wantarray ? (user_id, username) : user_id;
use strict; use warnings;
if ($condition) {
require Compress::Zlib;
# import Compress::Zlib ();
}
Используйте либо название функции с полным квалификатором:
MyModule::mysub()
либо используйте ОО-интерфейс в Ваших модулях:
my $obj = new MyObject; $obj->method();
Если уж Вы экспортируете какие-либо функции, лучше делать их экспорт опциональным (EXPORT_OK вместо EXPORT)
# Используем удобный ограничитель
$s =~ s{/}{::};
# Это куда нагляднее, чем /\//::/;
# Строим регексп для матчинга чисел с плавающей точкой...
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.».
Кроме того, НЕ СЛЕДУЕТ использовать qx и system в коде контроллеров. Эти вызовы могут быть сосредоточены только в коде ядра системы и соответствующие функции обязательно должны быть покрыты тестами (проверяющими невозможность использования эксплойтов).
open my $fh, "> $filename" or die "Can't open $filename for writing: $!"; print $fh '....';
SELECT `$fieldname` FROM `$table`Для кросс-dbms приложений можно использовать DBI-метод quote_identifier:
my $quoted_fieldname = $dbh->quote_identifier( 'fieldname' ); my $sql = "SELECT $quoted_name FROM mytable";
$a = $b || $cлучше, чем
$a = $b ? $b : $cи тем более лучше, чем
if ($b) {
$a = $b;
}
else {
$a = $c;
}
Также
$a ||= $bкороче и элегантнее, чем
unless ($a) {
$a = $b;
}
$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;
mkdir $tmpdir, 0700 or die "can't mkdir $tmpdir: $!"; $username = get_user_name() and print "Username: $username\n";
@foo = grep !/^#/, @bar;
@foo = map { $_ * 10 } @bar;
$_ *= 10 for @bar;
При этом не переусердствуйте: не стоит использовать map в void-контексте, там где уместнее было бы использовать foreach.
Обычно требуется обработка всех элементов массива сразу, а в этом случае удобнее использовать циклы foreach или функции, обрабатывающие массив целиком (см. п 5.4.). В случае использования foreach, grep, map элементы массива при каждой итерации предстают в виде переменной "$_" (или, для циклов foreach, любой другой переменной, какой Вы укажете), с которой работать куда удобнее, чем каждый раз обращаться к элементам массива по индексу.
Кроме того, часто необходим доступ только к крайним элементам массива (например, идёт процесс поэлементного заполнения массива и на каждой идерации мы добавляем новый элемент). В этом случае рекомендуется пользоваться функциями push, pop, shift, unshift. Применение этих функций позволяет получить более простой, компактный и БЫСТРЫЙ код. Сравните:
$cnt = 0;
while (condition) {
...
$items[ $cnt++ ] = $value; # Калька с низкоуровневых языков
}
while (condition) {
...
push @items, $value; # Короче и быстрее
}
Если какие-то чужие тесты у Вас не проходят, то это повод либо поправить исходный код (свой или чужой — не важно), либо тесты, если они не актуальны.
При создании тестов необходимо придерживаться следующих правил:
Исключение составляют записи в логах — логи откатывать не нужно.
Исключение из этого правила составляют модификации, вносимые в специальные ТЕСТОВЫЕ внешние системы (через различные тестовые аккаунты). В тестовых аккаунтах допустимы любые действия, если они не приведут к невозможности дальнейшего использования этого аккаунта для тестирования.
Не делайте лишних движений и лишних разработок до тех пор, пока Вам реально не понадобиться подобная функциональность. Возможно, ситуация / требования изменятся и то, что Вы делаете, не понадобиться никогда.
Из книги «Программист-прагматик. Путь от подмастерья к мастеру».
Зачем?
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
Удачные примеры: «users», «domains«, «domain_shop_categories», «user_doc_attachments».
Неудачные примеры:
«statistics» — понятно, что имеет отношение к статистике,
но неясно что это за статистика и объекты какого вида храняться в таблице;
«queue» — понятно, что это какая-то очередь,
но что именно за очередь неясно и объекты какого вида храняться в таблице.
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEYПоле для первичного ключа может быть названо по другому и иметь другой тип, но это обязательно обсуждается с другими членами команды и выносится соответствующее обоснование.
REPLACE INTO wf_parking (hostname, uri_path, html, fwdto) VALUES (?, ?, ?, ?) -- ПРАВИЛЬНО
Никогда не делайте так:
REPLACE INTO wf_parking VALUES (?, ?, ?, ?) -- НЕПРАВИЛЬНО! Не перечислен список полей
Если имена полей явно не перечислять, то при любом изменении состава полей
таблицы, например, при добавлении нового поля — неминуемо получим ошибку
(«column count doesn't match value count» и т. п.).
[% company_name | html %].Во избежание атак XSS.
[% news = [ { date => '31.12.2007', title => 'Happy new Year' } ] %]
[% FOREACH n = news %]
[% IF ru %] русский текст [% ELSE %] english text [% END %]
Если при обработке шаблона, в случае отсутствия необходимых данных, возникают фатальные ошибки или предупреждения, это не позволяет применять процедуры автоматического тестирования, проверающие валидность всех шаблонов. В частности валидность шаблона нельзя будет в произвольный момент времени проверить с помощью утилит tpage или ttree.
<img> или <br>)
должны иметь на конце / (например, <br />).
<option selected="selected"> или <td
nowrap="nowrap">
<img alt="" /> вместо <IMG ALT=""
/>).
Краткий справочник:
| © | © | знак охраны авторского права (copyright) |
| ® | ® | знак зарегистрированных прав |
| ™ | ™ | символ зарегистрированного товарного знака |
| « | « | левая кавычка (левая ёлочка) |
| » | » | правая кавычка (правая ёлочка) |
| — | — | тире |
| – | – | короткое тире для обозначения интервалов |
| − | − | минус |
| ± | ± | плюс/минус |
| ° | ° | знак градуса |
| < | < | знак «меньше» |
| > | > | знак «больше» |
| ≤ | ≤ | знак «меньше или равно» |
| ≥ | ≥ | знак «больше или равно» |
| & | & | амперсанд |
| | «несжимаемый» пробел | |
| ¹ | ¹ | 1 в верхнем индексе |
| ² | ² | 2 в верхнем индексе |
| ³ | ³ | 3 в верхнем индексе |
| № | № | знак номера |
| ¢ | ¢ | знак цента |
| £ | £ | знак фунта |
| ¥ | ¥ | знак йены |
| € | € | знак евро |
| § | § | знак параграфа |
| · | · | точка (знак умножения) |
| ¼ | ¼ | одна четверть |
| ½ | ½ | одна вторая |
| ¾ | ¾ | три четверти |
| • | • | буллет |
| … | … | многоточие |
| ‘ ’ | ‘ ’ | одинарные лапки |
| „ | „ | открывающая лапка |
| “ | “ | закрывающая лапка |
| ” | ” | закрывающая английская лапка |
| ▲ | ▲ | стрелки |
| △ | △ | -- " -- |
| ▴ | ▴ | -- " -- |
| ▵ | ▵ | -- " -- |
| ▶ | ▶ | -- " -- |
| ▷ | ▷ | -- " -- |
| ▸ | ▸ | -- " -- |
| ▹ | ▹ | -- " -- |
| ► | ► | -- " -- |
| ▻ | ▻ | -- " -- |
| ▼ | ▼ | -- " -- |
| ▽ | ▽ | -- " -- |
| ▾ | ▾ | -- " -- |
| ▿ | ▿ | -- " -- |
| ◀ | ◀ | -- " -- |
| ◁ | ◁ | -- " -- |
| ◂ | ◂ | -- " -- |
| ◃ | ◃ | -- " -- |
| ◄ | ◄ | -- " -- |
| ◅ | ◅ | -- " -- |
<label><input type="checkbox" name="test" value="1">Чекбокс 1</label> <label><input type="radio" name="test" value="1">Кнопка 1</label>
Заполняя таблицу текстовыми или цифровыми данными полезно следовать правилам:
Взято отсюда.
Если сайт, на содержимое которого мы ссылаемся, поддерживает подключение как по HTTP, так и по HTTPS, можно написать так:
[% proto %]://sitename/pathгде proto — наименование текущего протокола.
Если же удалённый сервер, на содержимое которого мы ссылаемся, не умеет отдавать содержимое по HTTPS, то вообще не включаем это содержимое на страницах, отдаваемых через HTTPS:
[% UNLESS is_https %] код, ссылающийся на http [% END %]
<form action="..." method="post">
<div>
<h3>Заголовок</h3>
...
</div>
</form>
Размер отступа не регламентируется, однако он быть отличен от нуля ;).
Для облегчения тестирования локальной версии сайта, дабы не выкладывать её в интернет для проверки, установите валидатор локально.
var variable = 'Value'; alert( variable );
Буду рад услышать любые замечания и дополнения по данному документу. Пишите на адрес: despair {at} cpan {dot} org.
При перепечатке и цитировании документа на других сайтах — ссылка на оригинал обязательна.
Постоянный адрес этой страницы в интернете: http://www.reg.ru/coding_standards.
© Walery Studennikov <despair [at] cpan {dot} org>