Комментарий - всегда признак неудачи
Р. Мартин "Чистый код"
Больше и лучше, чем сказано Р. Мартина в 4-й главе "Чистого кода", не скажешь. От себя добавлю: комментарии нужно поддерживать в актуальном состоянии. Код развивается, написанное в комментарии теряет свою актуальность, если комментарий не править вместе с кодом. А последнее делают далеко не всегда. Как правило из-за спешки. В одном из проектов, где я работал, видел и такое:
// TODO: Actualize comment above
То есть с комментариями ситуация выглядит немного странно: сперва мы пишем код, требующий комментирования. Затем мы правим этот код и старые комментарии превращаются в "технический долг". Потом мы актуализируем комментарии. Если успеваем или не забываем. А не проще ли писать код, не требующий комментирования? Код, который легко читается на языке, на котором он написан?
Теперь о phpDoc-ах. По сути - особый комментарий, "новое прочтение" javaDoc. Изначальная цель - быстрое создание актуальной документации по коду. Точнее - по особым образом оформленным комментариям в коде. Но за "время жизни" phpDoc приобрёл ещё несколько дополнительных функций:
- Аннотации. В java они часть языка, здесь же под них приспособили phpDoc.
- Компенсация особенностей языка. (Типизация и т.п)
Давайте вспомним, когда вы (или в вашем проекте) последний раз генерировали документацию к коду? А когда последний раз ее читали? А когда нашли там что-то для себя полезное? Думаю, большинство ответит отрицательно как минимум на 2 из 3-х вопросов. Все по той же причине, что изложена у Р. Мартина - код является истиной в последней инстанции. И надёжней прочитать код, чем надеяться на то, что написано в комментарии. Лично я, работая в проприетарных проектах, чаще слышал: "мы будем генерировать доку", чем у кого-то доходили руки до этого. Получалось почти как в старой восточной притче: или проект умирал, так и не увидев автоматически сгенерированную документацию, или я из него уходил, так документацию и не дождавшись. То есть phpDoc ради документации в проприетарных проектах скорее миф, чем нечто реальное.
Тем не менее doc-блоки я использовал активно и регулярно. Именно ради типизации. И те, чей код я ревьювил, всегда получали замечания за отсутствие doc-блоков к методам и свойствам классов. Так было, пока не появился PHP 7, а затем и 7.1 с их типизацией аргументов и возвращаемых значений (type declaratios). Какое-то время я писал doc-блоки и в этих версиях PHP. Но при очередном рефакторинге поймал себя на том, что больше правлю doc-блоки к методам, чем пишу код. Причём doc-блоки эти не несут ничего полезного - все что нужно для понимания кода уже есть в языке. Какая польза, например, от вот такого doc-блока:
/** * Sets customer full name * * @param string $fullName Full name * * @return void */ public function setCustomerFullName(string $fullName): void { $this->customerFullName = $fullName; }
А теперь, допустим, мы решили string заменить на класс FullName. Метод теперь должен выглядеть так:
/** * Sets customer full name * * @param FullName $fullName Full name * * @return void */ public function setCustomerFullName(FullName $fullName): void { $this->customerFullName = $fullName; }
Попробуем внести необходимые изменения за один шаг при помощи PhpStorm. Нажимаем комбинацию клавиш ( Refactor->Change signature…, Command-F6 в MacOS) когда курсор стоит на названии метода, меняем тип параметра и… магии не случилось, в doc-блоке получаем:
/** * Sets customer full name * * @param FullName|string $fullName Full name * * @return void */
Допиливаем напильником руками до нужного вида.
С типом возвращаемого значения так не получится. Refactor->Change signature… в PhpStorm на момент написания этой статьи такого не умел. То есть изменения необходимо вносить вручную в двух местах. Аналогично получается, если нужно сменить название метода или параметра - ни одна IDE их description за вас не поменяет на что-то чуть более интересное, чем преобразование имени переменной или метода в комментарий ($somePropertyName -> Some property name)
Описания (description) к классам, методам переменным, константам по сути - те же комментарии с теми же проблемами, что и обычные комментарии. Существует мнение, что description в данном случае - "декларация о намерениях программиста". В таком случае в description содержится заведомо неверная информации поскольку намерения и то, что сделано на самом деле - две большие разницы. А комментарий, не соответствующий действительности куда хуже, чем полное отсутствие комментария.
Рефакторинг одного простого метода выливается в достаточно приличный геморрой. И ради чего? Ради документации, которую никто никогда не будет генерировать? Или ради двух божественных слов Code Style? Да, соглашения по оформлению кода нужны. Но они придумываются программистами для самих себя. Значит, если code style мешает быстро писать качественный код, то code style нужно менять.
Итак, если документацию мы генерировать не будем, то phpDoc необходим для аннотаций и для компенсации "особенностей языка":
- Для переменных и свойств классов - обязательно.
- Для функций и методов. Если нужно уточнить передаваемые или возвращаемые данные, например для массива однотипных объектов. Так же, необходимо перечислить все исключения, выбрасываемые методом (здравствуй, java, снова ты).
- Для классов в тех случаях, если класс использует магические методы для доступа к свойствам или методам.
- Для констант doc-блок бессмысленен и бесполезен. Во первых тега @const нет в природе, а тег @var применительно к константе смотрится смешно и дезинформирует. Во вторых PHP самостоятельно определяет тип константы и проверяет его при передаче в метод, если включён контроль типов. И IDE несоответствие типов подсвечивает (как и любую ошибку)
/** * @method float cube(float $argument) */ class Example { const CUBE_POWER = 3; /** @var string */ public $property; /** * @param int[] $data * @return int[] * * @throws RuntimeException */ public function checksEmptyData(array $data): array { if (!$data) { throw new RuntimeException('Data is empty'); } return $data; } /** * @param string $name * @param array $arguments * @return float * * @throws RuntimeException */ public function __call($name, $arguments) { if ($name == 'cube') { return pow($arguments[0], self::CUBE_POWER); } throw new RuntimeException(sprintf('Unknown method "%s"', $name)); } }