liru

четверг, 29 июля 2010 г.

Стандарт кодирования PHP

Для коллекции добавляю этот мунускрипт, чтобы было ясно чем костыли отличаются от не костылей.
  1. Соглашения по именованию PHP-кода
  2. Форматирование кода
  3. Комментирование кода


Содержание


  1. Соглашения по именованию PHP-кода
    1. Выбирайте правильные имена
    2. Имена классов
    3. Имена методов
    4. Имена атрибутов класса
    5. Имена аргументов в методах
    6. Имена переменных
    7. Имена элементов в массивах
    8. Имена переменных ссылок и функций возвращающих результат по ссылке
    9. Глобальные переменные
    10. Имена в define / Глобальный константы
    11. Статические переменные
    12. Имена функций
  2. Форматирование кода
    1. Правила расстановки фигурных скобок
    2. Правила по отступам/табуляциям/пробелам
    3. Правила расстановки скобок () рядом с операторами и функциями
    4. Форматирование блоков if then else
    5. Формат switch
    6. Использование continue, break и ?:
    7. Выравнивание блоков объявления переменных
    8. Короткие методы и функции
    9. Комментируйте пустые выражения
    10. Избегайте вложенных операторов присваивания
  3. Комментирование кода
    1. Несколько комментариев по комментариям
    2. Документация интерфейсов и реализаций
    3. Документация по директориям
    4. Документация к файлам php
    5. Документирование классов
    6. Документирование функций
    7. Документация по тегам Doxygen


Соглашения по именованию PHP-кода


Выбирайте правильные имена

Имена - сердце программирования. В прошлом люди верили, что если узнать настоящее имя человека, можно получить власть над ним. Если вы подберёте правильные имена, вы наделите себя и других (всех, кто придёт после вас) властью над кодом. Просьба не смеяться, всё серьёзно.
Имя является результатом продолжительного осмысления мира, в котором "живёт" тот или иной объект. Только тот программист, который понимает систему в целом, в состоянии дать объектам имена, вписывающиеся в концепцию создаваемой системы. Если имя подобрано правильно, то всё стоит на своих местах, отношения между объектами ясны, значения легко угадываются и все человеческие мотивировки и ожидания срабатывают как хотелось бы.
И если вы вдруг обнаружили, что все объекты вашего кода называются Фигня и Штуковина, то такой код вам стоит серьёзно пересмотреть.

Имена классов

  • Давайте классу имя тогда, когда вы знаете, что этот класс будет делать, как и для чего. Если вы этого не знаете, то вполне возможно, вы не продумали до конца концепцию модуля;
  • Наличие имён, составленных более чем из трёх слов, может привести к тому, что система будет путать различные объекты программы. Пересмотрите код программы. Прогоните код через контроль циклически избыточного кода CRC и посмотрите, не берут ли ваши объекты на себя больше задач, чем вы планировали;
  • Не поддавайтесь искушению присвоить производному классу имя производное от имени родительского класса. Лучше будет, если класс будет жить своей жизнью, кто бы ни был его родительским классом;
  • Иногда помогают суффиксы. Например, если в вашей системе используются различные агенты, то имя типа DownloadAgent несёт достаточную смысловую нагрузку;

Имена функций и методов

  • Как правило функция или метод совершают какое-либо действие, поэтому желательно, чтобы из имени было понятно, какое именно действие будет совершаться: CheckForErrors() [ИщиОшибки()] вместо ErrorCheck() [ПоискОшибок()]; DumpDataToFile() [СваливайДанныеВФайл()] вместо DataFile() [ФайлДанных()]. Кроме того, так легче будет отличить метод от класса.
  • Иногда помогают суффиксы:





    • Max - чтобы показать максимальное значение;
    • Cnt [count: количество, подсчёт] - чтобы показать текущее значение какого-либо счётчика;
    • Key - чтобы показать ключевое значение.
Например: RetryMax содержит максимальное количество возможных попыток, а RetryCnt - номер текущей попытки.
  • Префиксы тоже иногда нелишни:





    • Is - для обозначения вопроса. Где ни встретится вам Is, вы всегда будете знать, что это вопрос;
    • Get - получить значение;
    • Set - установить значение.
Например: IsHitRetryLimit() [примерно: это ли последняя попытка?].

Никаких аббревиатур заглавными буквами

Если в имени переменной содержится аббревиатура, лучше вместо всех заглавных оставить только первую букву заглавной, а остальные написать строчными.
Неправильно: GetHTMLStatistic().
Правильно: GetHtmlStatistic().

Обоснование
При формировании имён, содержащих сокращения, люди используют свои интуитивные системы по-разному, поэтому лучше придерживаться единой стратегии формирования имён для всех случаем и добиться тем самым предсказуемости именования. Возьмём, к примеру, NetworkABCKey. Заметьте, что C от ABC и K от Key воспринимаются больше как буквенное сочетание. В принципе, некоторые не возражают против аббревиатур целиком из заглавных, другие же их просто ненавидят; так что в разным проектах люди придерживаются разных стратегий.

Пример
function FluidOz()      //а не FluidOZ
function GetHtmlStatistic()     //а не GetHTMLStatistic

Имена классов

  • В качестве разделителей слов используйте заглавные буквы, строчные - для остальной части слов;
  • Первая буква в имени - заглавная;
  • Никаких underscore-ов ('_').

Обоснование
Из всех других вариантов многие выбрали этот как лучшее компромиссное решение.

Примеры
class NameOneTwo
class Name

Имена методов

  • Используйте те же правила, что и для имен классов.

Обоснование
  • Из всех других вариантов многие выбрали этот как лучшее компромиссное решение.

Примеры
class NameOneTwo
{
    public function DoIt() {};
    public function HandleError() {};
}

Имена атрибутов класса

  • Имена публичных атрибутов класса должны начинаться с символа 'm'.
  • Имена скрытых и защищённых атрибутов класса должны начинаться с символа '_'.
  • После символов 'm' и '_' используются те же правила как для имен классов.
  • Символы 'm' и '_' всегда предшествует другим модификаторам, как модификатор 'r' для ссылки.

Обоснование
  • Первый символ 'm' и '_' предотвращает любые коллизии с именами методов. Часто имена ваших методов и имена атрибутов будет аналогичны, особенно для методов доступа.

Примеры
class NameOneTwo
{
    public function VarAbc() {};
    public function ErrorNumber() {};
    private $_VarAbc;
    private $_ErrorNumber;
    public $mrName;
}

Имена аргументов в методах

  • Первая буква - всегда строчная;
  • Все остальные слова в имени начинаются с большой буквы, как при именовании классов.

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

Примеры
class NameOneTwo
{
    public function StartYourEngines(&$someEngine, &$anotherEngine) {
        $this->mSomeEngine = $someEngine;
        $this->mAnotherEngine = $anotherEngine;
    }
 
    public $mSomeEngine;
    public $mAnotherEngine;
}

Имена переменных

  • Используйте только строчные буквы;
  • В качестве разделителя слов используйте underscore('_').

Обоснование
  • При таком подходе область действия переменных более уловима;
  • Все переменные кода выглядят по-разному и легко распознаются при чтении.

Примеры
function HandleError($errorNumber)
{
    $error = new OsError;
    $time_of_error = $error->GetTimeOfError();
    $error_processor = $error->GetErrorProcessor();
}

Имена элементов в массивах

  • Именование элементов массивов происходит по правилам именования переменных;
  • В качестве разделителя слов используйте underscore ('_');
  • И не используйте в качестве разделителя дефис ('-').
  • При доступе к элементам массива лучше всего использовать одинарные кавычки

Обоснование
  • Если в качестве разделителя используется дефис, то при включении опции Magic Quotes вы получите сообщения об ошибках.

Примеры
$myarr['foo_bar'] = 'Hello';
echo $myarr[foo_bar] . ' world'; // выведет: Hello world
 
$myarr['foo-bar'] = 'Hello';
echo $myarr[foo-bar] . ' world'; // получим warning

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

  • Имя ссылки должно начинаться с буквы 'r'.

Обоснование
  • Чётко видно тип переменной.
  • Так легко различить методы, возвращяющие модифицируемый и немодифицируемый объект.

Примеры
class Test
{
    public &$mrStatus;
    public function DoSomething(&$rStatus) {};
    public function &rStatus() {};
}

Глобальные переменные

  • В именах глобальных переменных рекомендуется использовать префикс 'g'.

Обоснование
  • Такой префикс необходим для определения области действия переменных

Примеры
global $gLog;
global &$grLog;

Имена в define / Глобальный константы

  • Имена глобальных констант должны быть написаны в верхнем регистре с разделителем '_'.

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

Примеры
define("A_GLOBAL_CONSTANT", "Hello world!");

Статические переменные

  • Статические переменные должны начинаться с 's'.

Обоснование
  • Важно знать видимости переменной.

Примеры
function test()
{
    static $sStatus = 0;
}

Имена функций

  • Для функций PHP используйте правила C GNU: имена из строчных букв с underscore-ами ('_') в качестве разделителей.

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

Примеры
function some_bloody_function()
{
}



Форматирование кода


Правила расстановки фигурных скобок

Открывающая скобка ставится под соответствующим оператором и на одном отступе с ним:
if ($condition)       while ($condition)
{                     {
    ...                   ...
}                     }

Обоснование
  • Многие находят такой стиль более эргономичным и эстетичным.
  • Если вы используете текстовый редактор (например, vi), поддерживающий проверку на парность скобок, такой стиль будет более удобен. Почему? - спросите вы. Допустим, вы наткнулись на большой блок кода и желаете узнать, где же он заканчивается. Наводите курсор на открывающую скобку, жмёте нужную кнопку и редактор находит парную скобку.
Пример
if ($very_long_condition && $second_very_long_condition) //два очень длинных условия
{
    ...
}
else if (...)
{
    ...
}
Итак, для перемещения от блока к блоку вам понадобится стрелка вниз и кнопка поиска парной скобки. И не надо ёрзать и гнать до конца строки, чтобы там найти ту самую открывающую скобку.

Правила по отступам/табуляциям/пробелам

  • Отступ для каждого нового уровня - 1 символ табуляции;
  • Для отступов использовать табуляцию, для выравнивания используются пробелы;
  • Делайте столько отступов, сколько вам нужно, но не более того. Если вы делаете отступ более, чем четвёртого-пятого уровня, стоит подумать о вынесении кода в отдельный блок.

Обоснование
  • Если у всех табуляция настроена одинаково, то у всех всё выглядит одинаково;
  • Никто никогда не договорится об оптимальном количестве пробелов. Просто будьте последовательны. 4 пробела - наиболее распространённое явление;
  • С того момента, когда люди вознамерились ограничить количество уровней отступа, кажется, что ни разу они так ничего и не добились. Мы надеемся, что программисты сами примут мудрое решение о достаточной глубине отступов.

Правила расстановки скобок () рядом с операторами и функциями

  • Не ставьте скобки сразу за операторами. Разделите оператор и скобки пробелом;
  • Ставьте скобки непосредственно сразу за именем функции;
  • Не используйте скобки в блоке return, если нет в этом необходимости.

Обоснование
  • Операторы - это не функции. Если поставить скобки сразу за оператором, функции и операторы будут выглядеть почти одинаково.

Примеры
if (condition)
{
}
 
while (condition)
{
}
 
strcmp($s, $s1);
 
return 1;

Форматирование блоков if then else


Внешний вид

  • Фигурные скобки должны расставлятся в соответствии с правилами указанными выше.
if (condition)                 // Комментарий
{
}
else if (condition)            // Комментарий
{
}
else                           // Комментарий
{
}
  • Если у вас в условном блоке есть else if, то стоит поставить else для всех необработанных значений. Даже если не предпринимаются никакие действия, это может быть простая запись в лог.

Формат условия

При сравнении всегда ставьте константы слева. Например:
if ( 6 == $errorNum ) ...
Первая причина такому поведению - это то, что парсер найдёт ошибку, если вы поставите только один знак равенства ('=') вместо двух. Вторая причина - при чтении кода вы находите нужное вам значение сразу в начале условия, а не ищите где-то в конце. К такому формату привыкаешь не сразу, но этот стиль действительно полезен.

Проверка ненулевого значения

Не оставляйте проверку на ненулевое значение дефолтному поведению php, т.е.
if (FAIL != f())
лучше чем
if (f())
,где FAIL может иметь значение 0, которое PHP считает равным false.
Явная проверка поможет вам позже, когда кто-то решит, что возвращаемое значение должно быть -1 вместо 0. Явные сравнения следует использовать, даже если сравниваемые значения никогда не изменятся, например, if (!($bufsize % strlen($str))) должно быть написано как if (0 == ($bufsize % strlen($str))), чтобы отразить числовой (не логический) характер сравнения. Наиболее часто эта ошибка встречается при использовании функции strcmp для сравнения строк, в этом случае значение по умолчанию никак не совпадает со смыслом возвращаемого значения.
Проверка по умолчанию на ненулевое значение возможна только для предикатов и других функций или выражений, которые удовлетворяют следующим ограничениям:
  • В качестве ложного значения возвращает 0 и больше ничего.
  • Названа так, что смысл возврата истинного значения является абсолютно очевидным. Например, используется предикат IsValid(), но не CheckValid().

Противоречие логического типа

Не применяйте логическое сравнение, для сравнения с 1 (TRUE, YES, и т.д.); вместо этого используйте сравнение на неравенство 0 (FALSE, NO, и т.д.). Многие функции гарантируют возврат 0 в качестве false, но при этом гарантируют только возврат ненулевого значения в качестве истины.
Таким образом,
if (TRUE == func()) { ...
должно быть переписано, как
if (FALSE != func()) { ...

Формат switch

  • При наличии соответствующего комментария допускаются блоки, передающие управление вниз;
  • Рекомендуется всегда ставить блок default, который бы сообщал об ошибке в случаях, когда попадание на него должно быть исключено, но тем не менее имело место;
  • Если вам нужно создать какие-либо переменные, то весь соответствующий код ставьте внутри блоков case.

Примеры
switch (...)
{
    case 1:
        ...
 
      // УПРАВЛЕНИЕ ПЕРЕДАЁТСЯ ВНИЗ
 
    case 2:
    {
        $v = get_week_number();
         ...
    }
    break;
 
    default:
}

Использование continue, break и ?:


Continue и break

Continue и break - это тот же самый goto, только названы по-другому. Именно поэтому они рассмотрены в этой части документа.
Как и goto, continue и break творят всякие разные чудеса в коде, поэтому их использование рекомендуется свести до минимума. Одним мановением руки читатель кода переносится бог знает куда по какой-то незадокументрированной причине.
При использовании continue возникают две проблемы:
  • continue может обойти условный блок;
  • continue может обойти наращивание/уменьшение.

Пример
Представим себе ситуацию, где имеют место обе проблемы:
while (TRUE)         
{
   ...
   // Много кода
   ...
 
   if (/* какое-то условие */) {
      continue;
   }
   ...
 
   // Много кода
   ...
   if ( $i++ > STOP_VALUE) break;
}






    • Note**: "много кода" нужно для того, чтобы программист не смог легко отследить проблему.
Из приведённого выше примера мы можем составить себе следующее правило: при использовании continue и break в одном блоке нужно быть очень осторожным, лучше не использовать такие конструкции.

 ?:

Проблема обычно заключается в том, что люди пытаются запихать слишком много кода между ? и :. Вот несколько правил:
  • Условие заключайте в скобки, тем самым отделяя его от остального кода;
  • По возможности действия, производимые по условию, должны быть простыми функциями;
  • Если весь блок ветвления плохо читается, будучи расположен на одной строке, то блоки else и then размещайте каждый на отдельной строке.

Пример
(условие) ? funct1() : func2();
или
(условие)
    ? длинный блок
    : ещё один длинный блок;

Выравнивание блоков объявления переменных

  • Блок объявления переменных должен выравниваться.
  • Знак & должен ставиться при типе переменной, а не при её имени.
  • Блоки инициализации переменных также рекомендуется выравнивать.

Обоснование
  • Прозрачность стиля.

Примеры
var       $mDate
var&      $mrDate
var&      $mrName
var       $mName
 
$mDate    = 0;
$mrDate   = NULL;
$mrName   = 0;
$mName    = NULL;

Короткие методы и функции

  • Желательно чтоб код метода или функции помещался на одну страницу кода.

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

Комментируйте пустые выражения

Всегда комментируйте пустые тела циклов for или while, это даст понять, что тело не забыто по ошибке, а так и должно быть.
while ($dest++ = $src++)
      ;         // VOID

Избегайте вложенных операторов присваивания

Существует время и место для встроенного оператора присваивания. В некоторых конструкциях нет лучшего способа для достижения результата без уменьшения его читабельности.
while ($a != ($c = getchar()))
{
    обработка символа
}
Операторы ++ и -- также считаются операторами присваивания. Таким образом, для многих целей, они могут использоваться для получения побочного эффекта. Использование встроенного оператора присваивания для увеличения производительности также возможно. Однако, следует рассматривать компромисс между увеличением скорости и уменьшением поддерживаемости, что может случаться, когда встроенные присваивания используются искусственно. Например,
$a = $b + $c;
$d = $a + $r;
не должно заменятся на
$d = ($a = $b + $c) + $r;
хотя последний вариант может сохранить несколько тактов процессора. В долгосрочной перспективе разница во времени между двумя вариантами будет уменьшаться по мере улучшения оптимизаторов, в то время как разница в простоте поддержки кода будет возрастать по мере забывания человеком того, что происходит в данном куске кода.



Комментирование кода


Несколько комментариев по комментариям


Комментарии должны быть содержательными

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

Документируйте принятые решения

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

Используйте заголовки

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

Явно указывайте на gotchas ["ловушки" в коде]

Комментируйте как любые изменения переменных, не совсем вписывающиеся в нормальный ход программы, так и все конструкции, которые могут натворить дел при изменении кода. Для явного указания на проблемные или потенциально проблемные куски кода используйте встроенные зарезервированные слова.

Формат описания gotchas
  • Ключевое слово gotcha должно ставиться в самом начале комментария.
  • Комментарии могут содержаться на нескольких строках, но первая строка должна быть ясным и законченным по смыслу конспектом.
  • Комментарий должен включать в себя имя автора и дату замечания. Эти сведения содержатся и в документации исходников, но иногда бывает трудно их отыскать. Иногда случается, что потенциально опасный участок кода слишком долго не исправляется. Дата gotcha позволяет определить такие места. Указание на автора gotcha показывает, с кого нужно спросить.
Пример:
// :TODO: tmh 960810: возможны проблемы со скоростью работы
// Нам точно понадобится хэш-таблица, но пока
// мы используем линейный поиск.
 
// :KLUDGE: tmh 960810: потенциально небезопасное приведение типа
// Приведение понадобилось для восстановления производного типа. Возможно
// здесь понадобится виртуальный метод или шаблон.

Зарезервированные слова для описания gotchas
  •  :TODO: Означает, что здесь нужна доработка, простое напоминание.
  •  :BUG: [id] Означает, что здесь находится Выявленная ошибка, дайте описание и (не обязательно) номер ошибки (Номер ошибки это номер тикета RT если он был создан).
  •  :KLUDGE: Если то, что вы сделали, уродливо выглядит и работает, отметьте этот факт и объясните, как вы поступите в следующий раз, если у вас будет время.
  •  :TRICKY: Сообщение о том, что данный отрезок кода очень сложен в исполнении и потому не стоит ничего менять, не разобравшись предварительно во всех "коварствах" конструкции.
  •  :WARNING: Предупреждение о чём-либо.
  •  :PARSER: Иногда вам приходится что-то делать для успешного парсинга. Задокументируйте этот факт. Возможно, со временем проблема исчезнет.
  •  :ATTRIBUTE: Общий вид представления атрибута в комментарии. Вы можете создать свои атрибуты, и они тоже будут извлечены роботом.



Документация интерфейсов и реализаций

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

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

Разработчики класса
Разработчикам класса требуется глубокое знание реализации класса. Комментарии этого типа находятся в файлах с исходным кодом класса; забудьте на время про особенности интерфейса. Заголовочный блок комментариев в исходнике должен описать все особенности алгоритма и решения по проектированию класса. Блоки комментариев внутри методов реализации должны предоставить ещё более подробную информацию.

Документация по директориям

В каждой директории проекта должен находиться файл README с описанием следующих моментов:
  • Зачем была создана данная директория и что она содержит;
  • По строчке описания каждого файла этой директории. Как правило, описание берётся из значения атрибута NAME в заголовке файла;
  • Инструкции по сборке и установке;
  • Ссылки на связанные источники: директории исходников, online документация, документация на бумажных носителях, документация по разработке;
  • И всё, что как-либо может помочь.
Представьте себе, что полгода после ухода последнего из зачинателей проекта в команду приходит новый человек. Этот одинокий и напуганный странник должен по кусочкам собрать и воссоздать полную картину проекта, пройдясь по директориям исходников и прочтя все README, make-файлы и заголовки в файлах исходников.

Документация к файлам php

Каждый файл, содержащий PHP-код должен иметь заголовочный блок в начале файла, следующего вида:
//--------------------------------------------------------------------------------
/// @file
/// @brief Краткое описание файла
/// @author Автор 
/// @date дата создания файла
/// @version $Id: $
/// 
/// Длинное описание файла (если есть)...
///
//--------------------------------------------------------------------------------

Документирование классов

Классы PHP должны документироваться следующим образом:
//--------------------------------------------------------------------------------
/// @class Test
/// @brief Краткое описание
/// @author Автор1 
/// @author Автор2 
/// 
/// Длинное описание (если есть)...
//--------------------------------------------------------------------------------
class Test
{
    public $mVar1; ///< описание переменной $mVar1
    private $_Var2; ///< описание переменной $_Var2
 
    //--------------------------------------------------------------------------------
    /// @brief Краткое описание
    /// @param $var1 описание параметра
    /// @param $var2 описание параметра
    /// @return описание возвращаемого значения
    /// @throw exeption описание исключения (если метод может выбросить исключение)
    /// @author Автор1 
    /// @author Автор2 
    /// 
    /// Длинное описание (если есть)...
    //--------------------------------------------------------------------------------
    public function method1($var1, $var2){
        return $var1+$var2;
    }
}

Документирование функций

Формат документации к функции совпадает с форматом комментария для метода класса.

Документация по тегам Doxygen

Для получения дополнительной информации о тегах doxygen воспользуйтесь документацией к соответствующему продукту :) .

Комментариев нет:

Отправить комментарий