← Заметки и статьи о Laravel PHP

28 января 2017 перевод

Оформляем код рациональнее

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

Замыкающие запятые

Должны ли мы использовать замыкающие запятые? Если вы не знакомы с этой практикой, вот пример:

$flavors = [
   'chocolate',
   'vanilla',
];

Последний элемент массива замыкается запятой, несмотря на отсутсвие последующего. Это абсолютно валидный код в PHP. Замыкающая запятая просто игнорируется.

Теперь уберём её:

$flavors = [
   'chocolate',
   'vanilla'
];

И добавим новый элемент:

$flavors = [
   'chocolate',
   'vanilla',
   'lemon'
];

Такой diff у нас получится при коммите:

$flavors = [
    'chocolate',
-   'vanilla'
+   'vanilla',
+   'lemon'
];

А такой — с использованием замыкающей запятой:

$flavors = [
   'chocolate',
   'vanilla',
+  'lemon',
];

С замыкающей запятой коммит чище и проще. Это особенно полезно при использовании git blame: каждая строка будет вести к реальным данным, добавленным в коммите.

Вывод: используйте замыкающие запятые.

Выравнивание значений

Эта практика достаточно часто используется, но PSR-2 не определяет этот момент. Вот пример в phpdoc:

/**
 * @param int             $id       ID of the thing to export.
 * @param string          $filename Name of the file in which to export.
 * @param LoggerInterface $logger   Used to log the progress of the export.
 */

С массивами:

$formTypes = [
   'text'     => new TextField,
   'select'   => new SelectField,
   'checkbox' => new CheckboxField,
];

С присваиваниями:

$firstname = 'Spongebob';
$lastName  = 'Squarepants';
$age       = 25;

Каждый, кто сталкивался с таким кодом, знает, как сложно его редактировать. Он может выглядеть красиво, но при редактировании придётся сохранять выравнивание, добавляя пробелы.

Можно сказать, что IDE сама может выровнять код, но давайте взглянем на diff коммитов:

$formTypes = [
-   'text'     => new TextField,
-   'select'   => new SelectField,
-   'checkbox' => new CheckboxField,
+   'text'           => new TextField,
+   'select'         => new SelectField,
+   'checkbox'       => new CheckboxField,
+   'my_custom_type' => new CheckboxField,
];

Коммит выглядит нечитаемым и информация в git blame искажена.

Ещё один пример: рефакторинг в IDE кода phpdoc выше (LoggerInterface переименован в Logger):

/**
 * @param int             $id       ID of the thing to export.
 * @param string          $filename Name of the file in which to export.
 * @param Logger $logger   Used to log the progress of the export.
 */

Мы все встречались с этим. Выравнивание значений требует больше усилий в поддержке, портит diff коммитов и git blame.

Вывод: не используйте выравнивание значений.

Оформление PHPDOC

Использование phpdoc — это хорошая практика. Но иногда хорошо задокументированный код выглядит так:

/**
 * Constructor.
 *
 * @param HttpKernelInterface $kernel An HttpKernelInterface instance
 * @param string $cacheDir The cache directory (default used if null)
 */
public function __construct(HttpKernelInterface $kernel, $cacheDir = null)
{ ... }

В нём много текста, но он дублирует в своём большинстве сам код:

  • мы видим конструктор по названию метода
  • мы видим, что $kernel — это HttpKernelInterface и входящий объект должен реализовывать этот интерфейс

Что действительно полезно в phpdoc:

  • $chacheDir — это строка (или null)
  • если $chacheDir null, то будет использоваться директория по умолчанию

В итоге этот phpdoc можно переписать так:

/**
 * @param string|null $cacheDir The cache directory (default used if null)
 */
public function __construct(HttpKernelInterface $kernel, $cacheDir = null)
{ ... }

С PHP7 мы можем пойти ещё дальше и отказаться от phpdoc в некоторых случаях. Например:

interface KernelInterface
{
    ...

    /**
     * Gets the name of the kernel.
     *
     * @return string The kernel name
     */
    public function getName();
}

В PHP7 превратится в:

interface KernelInterface
{
    ...

    public function getName() : string;
}

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

Суффикс Interface

Эта тема уже обсуждалась, поэтому перейдём сразу к сути: в большинстве случаев нет необходимости добавлять суффикс Interface к имени интерфейса.

Например:

class Foo
{
    public function __construct(CacheInterface $cache)
    { ... }
}

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

Мой ответ таков: не важно, интерфейс это или реализация. Что важно, так это то, что Foo нужен кеш, конец истории. Под интерфейсами стоит принцип: или у тебя есть одна реализация и тебе не нужен интерфейс, или у тебя несколько реализаций и тебе нужен интерфейс.

Если по какой-то причине классу нужна только одна реализация интерфейса, то дайте ему его напрямую. Если классу могут понадобиться разные реализации, значит, каждая из них существует для конкретного случая. И это должно быть видно в имени. Нет причин делать одну реализацию и называть её Cache. Будут RedisCache, FileCache, ArrayCache и др.

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

Это может нарушить нашу привычку: нам придётся или называть классы лучше, или убирать интерфейсы.

Приведём пример: UserRepository — это интерфейс, значит нам придётся искать более специфичные имена для реализаций. Например, DoctrineUserRepository, EloquentUserRepository, PdoUserRepository или InMemoryUserRepository. В интерфейсах больше смысла, если они забирают имя по умолчанию. Реализации вторичны.

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

Суффикс Exception

Вот где мы используем этот суффикс в PHP:

  • throw ...
  • catch (...)
  • @throws в phpdoc
  • при создании класса исключения из другого исключения

Это значит, что где бы ни возник класс исключения среди этого перечня, мы точно знаем, что это исключение. Суффикс Exception лишний.

Например: UserNotFoundException. Суффикс не несёт никакого значения. UserNotFound — это всё, что нам нужно.

Вот что интересно: иногда удаление суффикса делает имена классов неудачными. Например, возьмём исключения из Symfony Form.

Сначала посмотрим на классы, которые нормально читаются без суффикса:

  • BadMethodCall
  • InvalidArgument
  • InvalidConfiguration
  • OutOfBounds
  • TransformationFailed
  • UnexpectedType

А теперь взглянем на те, что больше похожи на информацию, чем на ошибку:

  • AlreadyBound
  • AlreadySubmitted

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

Вернёмся к нашему примеру: как насчёт CannotRebind и CannotSubmitAgain?

И напоследок посмотрим, что у нас осталось:

  • ErrorMappingException (комбо — слово error и exception)
  • LogicException
  • RuntimeException
  • StringCastException

Кроме RuntimeException и LogicException, которые крайне общие (может, они заслужили большей специфичности?), вот предложение именования для оставшихся:

  • InvalidMapping
  • CannotBeCastToString

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

Вывод: суффикс Exception лишний. Старайтесь быть более специфичными в описании ошибок.

Заключение

Старайтесь подходить к оформлению кода с рациональной стороны. Не бойтесь нарушать свои привычки, просто попробуйте.

Если всё прочитанное в этом переводе — это bullshit, то пишите комменты к статье автора. Ссылка ниже.

Источник: http://mnapoli.fr/approaching-coding-style-rationally/

Плюсануть
Поделиться
Отправить