FurryCat 26 сентября 2019

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

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

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

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

Чтобы понять, что действительно хорошо, пойдем от противного и попробуем разобраться, что плохо, вдохновляясь 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-проектов страдают от тавтологического синдрома и большая часть из них никогда не будет вылечена. Но в наших силах создать новое поколение здоровых и красивых приложений!

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

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

МЕРОПРИЯТИЯ

Митап Управление проектами» в Ульяновске
28 марта Ульяновск Бесплатно
Искать мидлов или растить джунов?
26 февраля Онлайн Бесплатно
@Databases Meetup #1
28 февраля Москва Бесплатно
.NET Z-meet up
27 февраля Санкт-Петербург Бесплатно

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

ВАКАНСИИ

Frontend разработчик (React)
от 100000 RUB до 160000 RUB
Unity 3D Developer
по итогам собеседования
Python developer
Москва, от 120000 RUB до 160000 RUB

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

BUG