FurryCat 26 сентября 2019
76
46430

Интерфейсный интерфейс, или три плохих совета для именования

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

Сколько бессонных ночей мы провели, подбирая идеальное наименование для переменных, функций и файлов – увы, не всегда успешно. Сколько тысяч вариантов перебрали, сколько нервов потратили! А все ради чего? Чтобы наш код выглядел логичным и единообразным.

Чтобы облегчить муки подбора, великие умы придумали соглашения о наименованиях. Но теперь появилась новая проблема – какому соглашению следовать?

Чтобы понять, что действительно хорошо, пойдем от противного и попробуем разобраться, что плохо, вдохновляясь Doctrine Coding Standard и демо-проектом Proophessor Do.

Антипаттерн #1. Тавтология интерфейсов

Начнем с классической тавтологии – добавлении префикса/суффикса Interface к – чему бы вы думали! – названиям интерфейсов.

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

В повседневном языке тавтологии обычно считаются недостатком стиля. "Круглый круг", "сухая пустыня", "новые инновации" – все это не самые изящные словосочетания. Та же проблема постоянно встречается при использовании аббревиатур, например, "система GPS" или "номер ISBN".

А теперь прочтите вслух определение этого PHP-класса:

            interface TodoRepositoryInterface
{
} 
        

Снова тавтология!

Не все разработчики обращают внимание на подобные вещи. Однако если задуматься, это соглашение об именовании кажется довольно глупым. Вы ведь не используете суффикс Class для имен ваших классов: TodoClass вместо простого Todo. Чем интерфейсы хуже?

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

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

Современные IDE также прекрасно отличают интерфейсы:

Те же самые аргументы можно применить к абстрактным классам и трейтам с соответствующими префиксами/суффиксами Abstract  и Trait. Однако это может быть приемлемым исключением из правила, ведь абстрактные классы не должны быть частью какого-либо публичного интерфейса. Вы также можете использовать альтернативный префикс, например, Base, но не стоит злоупотреблять этим подходом. Практически в любой ситуации можно придумать хорошее имя, просто описав цель и область действия абстрактного класса. К примеру, можно поместить логику, общую для всех PDO-репозиториев в абстрактный класс PdoRepository, а MySqlTodoRepository станет конкретной реализацией.

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

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

Антипаттерн #2. Тавтология доменных классов

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

С сущностями и Value Objects проблем обычно не возникает, ведь мы обычно тщательно выбираем для них хорошие имена: User, EmailAddress, Todo, TodoText. Но дальше начинаются сложности:

            namespace My\Todo\Exception;

final class CannotReopenTodoException extends \Exception
{

} 
        

Суффикс Exception особенно не бросается в глаза, как это было с интерфейсами. Но давайте посмотрим на FQCN \My\Todo\Exception\CannotReopenTodoException.

Снова тавтология, ведь Exception уже встречается в пространстве имен.

Кроме того, формулировка и контекст использования класса CannotReopenTodoException (throw, catch, имя переменной $ex) однозначно указывают на то, что мы имеем дело с исключением:

            try {
    throw CannotReopenTodoException::notDone($todo);
} catch (CannotReopenTodoException $ex) {

} 
        

Очевидно, что суффикс Exception тут лишний.

Еще одна неотъемлемая часть архитектуры – это события, которые часто именуются по такому же принципу. Действительно, в имени FooEvent суффикс необходим, иначе мы не поймем, что речь идет о событии. Однако было бы лучше использовать точное описание действия в прошедшем времени – TodoWasMarkedAsDone.

В этом паттерне большое значение имеет понятность имен. Если название класса достаточно хорошо описывает его смысл, суффикс можно (и нужно) удалить. Плохое имя чаще всего – дефект дизайна. Если вы не можете изменить его, значит, что-то в вашем коде пошло не так. 

Антипаттерн #3. Тавтология гет-методов

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

Геттеры и сеттеры – основная форма взаимодействия со свойствами объекта через публичный интерфейс. Учитывая, что объекты-значения иммутабельны, а сущности поощряют инкапсуляцию, у них не должно быть классических сеттеров. В такой ситуации геттеры берут на себя роль простых методов доступа к свойствам.

            final class Todo
{

    public function getId(): TodoId
    {
    }

    public function getDescription(): string
    {
    }

    public function getStatus(): Status
    {
    }

} 
        

И здесь префикс get становится лишним. Он не несет никакой полезной информации – разве что группирует все методы доступа.

Альтернативное "strip naked" соглашение об именах для методов доступа к свойствам – это важный шаг к общему стандарту оформления сущностей и объектов-значений. В PHP 7.4 стали доступны типизированные свойства и, может быть, скоро появятся readonly свойства, так что в ближайшем будущем мы, возможно, сможем писать наши классы вот так:

            final class Todo
{
    public TodoId readonly $id;

    public string readonly $description;

    public Status readonly $status;
} 
        

Нет тавтологии!

Множество существующих PHP-проектов страдают от тавтологического синдрома и большая часть из них никогда не будет вылечена. Но в наших силах создать новое поколение здоровых и красивых приложений! 

Шпаргалка на память

РУБРИКИ В СТАТЬЕ

Комментарии 76

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

BUG