Java Annotations Are a Big Mistake

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

Аннотации были введены в Java 5, и все мы были в восторге. Такой отличный инструмент для сокращения кода! Нет больше файлов конфигурации Hibernate/Spring XML! Просто аннотации, прямо в коде, где они нужны. Больше нет маркерных интерфейсов, только аннотация, сохраняемая во время выполнения и открываемая с помощью рефлексии! Я тоже был в восторге. Более того, я создал несколько библиотек с открытым исходным кодом, которые активно используют аннотации. Возьмем, к примеру, jcabi-aspects. Однако, я больше не в восторге. Более того, я считаю, что аннотации - это большая ошибка в проектировании Java.

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

Предположим, что мы аннотируем свойство с помощью @Inject:

Затем у нас есть инжектор, который знает, что внедрять.

Теперь мы создаем экземпляр класса Books через контейнер:

Класс Books не имеет представления о том, как и кто будет внедрять экземпляр класса DB в него. Это произойдет за кулисами и вне его контроля. Внедрение выполнит это. Это может показаться удобным, но такое отношение наносит большой ущерб всей кодовой базе. Утрачивается контроль (не инвертированный, а утраченный!). Объект больше не отвечает за то, что с ним происходит.

Вместо этого, вот как это следует сделать:

В этой статье объясняется, почему контейнеры внедрения зависимостей - неправильная идея: контейнеры внедрения зависимостей загрязняют код. Аннотации по сути заставляют нас создавать и использовать контейнеры. Мы перемещаем функциональность вне наших объектов и помещаем ее в контейнеры или в другие места. Ведь мы не хотим дублировать один и тот же код снова и снова, верно? Это правильно, дублирование - плохо, но расчленение объекта еще хуже. Намного хуже. То же самое верно и для ORM (JPA/Hibernate), где активно используются аннотации. В этом сообщении объясняется, что не так с ORM: ORM - это оскорбительный антипаттерн. Самыми главными мотиваторами не являются сами аннотации, но они помогают и поддерживают нас, расчленяя объекты и храня их части в разных местах. Это контейнеры, сессии, менеджеры, контроллеры и т.д.

Вот как работает JAXB, когда вы хотите преобразовать свой POJO в XML. Сначала вы присоединяете аннотацию @XmlElement к геттеру:

Затем вы создаете маршаллер и просите его преобразовать экземпляр класса Book в XML:

Кто создает XML? Не book. Кто-то другой, вне класса Book. Это очень неправильно. Вместо этого, это должно было быть сделано так. Сначала класс, который не имеет представления о XML:

Затем декоратор, который печатает его в формат XML:

Теперь, чтобы распечатать книгу в формате XML, мы выполняем следующие действия:

Функциональность печати XML находится внутри XmlBook. Если вам не нравится идея с декоратором, вы можете переместить метод toXML() в класс DefaultBook. Это не так важно. Важно то, что функциональность всегда должна находиться там, где ей принадлежит - внутри объекта. Только объект знает, как распечатать себя в формате XML. Никто другой!

Вот пример (из моей собственной библиотеки):

После компиляции мы запускаем так называемый AOP-плетчик, который технически преобразует наш код в нечто подобное:

Я упростил действующий алгоритм повторной попытки вызова метода при ошибке, но, я уверен, вы понимаете идею. AspectJ, движок AOP, использует аннотацию @RetryOnFailure в качестве сигнала, информируя нас о том, что класс должен быть обернут в другой класс. Это происходит за кулисами. Мы не видим этот дополнительный класс, который реализует алгоритм повторной попытки. Но байт-код, созданный компоновщиком AspectJ, содержит измененную версию класса Foo.

Вот что именно не так с этим подходом - мы не видим и не контролируем создание этого дополнительного объекта. Композиция объектов, которая является наиболее важным процессом в объектном дизайне, скрыта где-то за кулисами. Вы можете сказать, что нам не нужно видеть это, так как это дополнительное. Я не согласен. Мы должны видеть, как наши объекты составляются. Нам может быть не важно, как они работают, но мы должны видеть весь процесс соединения.

Гораздо лучшим решением было бы следующее (вместо аннотаций):

А затем реализация FooThatRetries:

И теперь реализация Retry:

Код стал длиннее? Да. Стал ли он более чистым? Гораздо больше. Жалею, что два года назад я не понимал этого, когда начал работать с jcabi-aspects.

В конечном счете, аннотации плохи. Не используйте их. Что должно использоваться вместо них? Объектная композиция.

Что может быть хуже аннотаций? Конфигурации. Например, XML-конфигурации. Механизмы конфигурации Spring XML - прекрасный пример ужасного дизайна. Я уже много раз говорил об этом. Позвольте мне повторить еще раз - Spring Framework является одним из самых плохих программных продуктов в мире Java. Если вы сможете избегать его, вы сделаете себе большую услугу.

В ООП не должно быть никаких “конфигураций”. Мы не можем настраивать наши объекты, если они являются реальными объектами. Мы можем только создавать их экземпляры. И лучший метод создания экземпляров - оператор new. Этот оператор является ключевым инструментом для разработчика ООП. Забрать его у нас и предоставить “механизмы конфигурации” - это непростительное преступление.

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-05 at 21:34

sixnines availability badge   GitHub stars