Интерфейсный интерфейс, или три плохих совета для именования
Во все времена самым сложным делом в программировании было наименование. Подбирать имена для классов за вас мы, конечно, не будем, но кое-что посоветуем.
Сколько бессонных ночей мы провели, подбирая идеальное наименование для переменных, функций и файлов – увы, не всегда успешно. Сколько тысяч вариантов перебрали, сколько нервов потратили! А все ради чего? Чтобы наш код выглядел логичным и единообразным.
Чтобы облегчить муки подбора, великие умы придумали соглашения о наименованиях. Но теперь появилась новая проблема – какому соглашению следовать?
Чтобы понять, что действительно хорошо, пойдем от противного и попробуем разобраться, что плохо, вдохновляясь Doctrine Coding Standard и демо-проектом Proophessor Do.
Антипаттерн #1. Тавтология интерфейсов
Начнем с классической тавтологии – добавлении префикса/суффикса Interface
к – чему бы вы думали! – названиям интерфейсов.
Тавтология – это структура речи, в которой одна и та же идея выражается с помощью очень похожих морфем, слов или фраз. Иначе говоря, повторяется дважды без значимого обоснования.
В повседневном языке тавтологии обычно считаются недостатком стиля. "Круглый круг", "сухая пустыня", "новые инновации" – все это не самые изящные словосочетания. Та же проблема постоянно встречается при использовании аббревиатур, например, "система GPS" или "номер ISBN".
А теперь прочтите вслух определение этого PHP-класса:
Снова тавтология!
Не все разработчики обращают внимание на подобные вещи. Однако если задуматься, это соглашение об именовании кажется довольно глупым. Вы ведь не используете суффикс 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
. Но дальше начинаются сложности:
Суффикс Exception
особенно не бросается в глаза, как это было с интерфейсами. Но давайте посмотрим на FQCN \My\Todo\Exception\CannotReopenTodoException
.
Снова тавтология, ведь Exception
уже встречается в пространстве имен.
Кроме того, формулировка и контекст использования класса CannotReopenTodoException
(throw
, catch
, имя переменной $ex
) однозначно указывают на то, что мы имеем дело с исключением:
Очевидно, что суффикс Exception
тут лишний.
Еще одна неотъемлемая часть архитектуры – это события, которые часто именуются по такому же принципу. Действительно, в имени FooEvent
суффикс необходим, иначе мы не поймем, что речь идет о событии. Однако было бы лучше использовать точное описание действия в прошедшем времени – TodoWasMarkedAsDone
.
В этом паттерне большое значение имеет понятность имен. Если название класса достаточно хорошо описывает его смысл, суффикс можно (и нужно) удалить. Плохое имя чаще всего – дефект дизайна. Если вы не можете изменить его, значит, что-то в вашем коде пошло не так.
Антипаттерн #3. Тавтология гет-методов
Этот паттерн не так однозначно плох, как первые два. Возможно даже, вы абсолютно не согласитесь с утверждением об избыточности префикса get
, но давайте хотя бы обсудим эту идею.
Геттеры и сеттеры – основная форма взаимодействия со свойствами объекта через публичный интерфейс. Учитывая, что объекты-значения иммутабельны, а сущности поощряют инкапсуляцию, у них не должно быть классических сеттеров. В такой ситуации геттеры берут на себя роль простых методов доступа к свойствам.
И здесь префикс get
становится лишним. Он не несет никакой полезной информации – разве что группирует все методы доступа.
Альтернативное "strip naked" соглашение об именах для методов доступа к свойствам – это важный шаг к общему стандарту оформления сущностей и объектов-значений. В PHP 7.4 стали доступны типизированные свойства и, может быть, скоро появятся readonly свойства, так что в ближайшем будущем мы, возможно, сможем писать наши классы вот так:
Нет тавтологии!
Множество существующих PHP-проектов страдают от тавтологического синдрома и большая часть из них никогда не будет вылечена. Но в наших силах создать новое поколение здоровых и красивых приложений!