Когда я впервые увидел - lombok, у меня возникло, дикое сопротивление. Было очевидное ощущение, что что-то не так. Я думаю, у многих консервативных разработчиков возникло такое же ощущение. Однако, lombok популярен. Люди его любят, люди его используют. А значит, есть и будут появляться проекты с ним. А значит нам с этим всем придется как-то жить.
Мои претензии к lombok
Кратко:
провоцирует менять доменную модель откуда вздумается и как вздумается при помощи setter-ов.
провоцирует писать бизнесовую логику где придется, получая необходимые данные из getter-ов.
сгенеренные builder-ы становятся тупыми, а могли бы быть чуть удобней.
он конфликтует с spring data jdbc в плане создания объектов из-за конструкторов.
есть проблемы с иерархией наследования.
откровенно не красивый код с ёлками из аннотаций в начале класса.
есть недочеты в API. например, аннотация Builder на классе, которая автоматом создает конструктор. Вот ни разу не ожидаешь подвоха.
слабая поддержка в плагине(даже на момент сейчас у меня к плагину есть вопросы по навигации, я уже молчу про времена, пару лет назад, когда выходила новая IDEA и плагин ломался).
Сюда можно ещё холивары добавить, но не буду, мое отношение и так понятно.
Как мы его готовим - best practics
На своих проектах я постарался выстроить некую систему правил использования lombok.
-
Аннотация
@Setter
- запрещена (если вы без них вообще никак, то вещайте на конкретные поля и пользуйте аккуратно, соблюдая слои приложения).Существует антипаттерн - цепочка setter-ов. Для изменения данных в объекте пиши свой метод. Описывай его бизнес смысл в имени метода и в javaDoc. Фиксируй логику метода в тестах. Проверяй состояние сущности в методе. И так далее ...
С этой аннотацией и её аналогами цепочки setter-ов на проекте начинают плодиться с устрашающей скоростью. Не успеваешь выбраковывать PullRequest-ы.
UPD: стоит оговориться, сейчас набежали в комментарии и напомнили, что существует такой подход к разработке ПО, как transaction script. Подход достаточно старый, но видимо всё ещё популярный в определенных кругах. Я не против самого подхода, есть задачи, где так действительно проще, но возникает вопрос - setter-ы, без них никак, а как иначе изменить
модельструктуру с данными из сервиса.Если бы перед мной стояла похожая задача, я бы объекты всё-равно делал бы умней, а ту логику которую можно вынести в модель, всё же вынес. Во-первых убрал бы дикий копипаст на однотипные изменения(и получение данных - к getter-ам это тоже относится). Во-вторых, у нас всё-таки ООП язык, процедурный стиль разработки сюда не к месту.
Если вы на проекте используете domain model подход(или думаете, что вы его используете), то вам setter-ы не нужны.
С учетом замечаний добавил оговорку курсивом.
-
Аннотация
@Getter
- разрешена только на поле, на классе нельзя.Это попытка уйти от размазывания логики работы с данными в 100-500 сервисах. Начнем с того, что это не ОПП, когда чужой класс начинает лезть в модель и разбираться в её структуре. Лучше сделать метод, который вернет необходимые данные этому чужому классу-сервису. Этот метод можно покрыть тестами и тем самым улучшить весь проект, который будет адекватно реагировать на снижение качества данных.
-
Аннотации
@Data/@Value
- запрещены.В современной java есть record - его достаточно.
UPD: допустим - у вас древняя java. Тогда для DTO -
@Value
- ваш выбор, как замена record.И уж точно вам не нужна
@Data
. DTO изменяемой быть не может, а на доменной модели setter-ы - как уже писал выше - нежелательны. -
Аннотация
@Builder
- можно на record, нельзя на класс, но можно на конструкторВыше уже писал, что аннотация
@Builder
- на классе (если это конечно, не record - там по-другому никак), не самое лучше решение с точки зрения API lombok. Потому что оно порождает конструктор со всеми параметрами не явно, чего не ожидаешь совсем. Однако, возможность вещать@Builder
на конструктор - оказывается приятным синтаксическим сахаром. Почему нет.На модельке с данными(чаще это доменная модель) возможно несколько конструкторов. Во-первых, это конструктор со всеми полями для работы ORM. А так же конструкторы для специальных задач, например, создание по-умолчанию, копирование, копирование из другой сущности и так далее. На все эти конструкторы мы в коде навешиваем аннотацию Builder, но с говорящим названием метода builder-а.
Например, builder для тестов мы назовем testEntity в надежде, что никто в продакшен коде не напишет код вида
Task.testEntity().field1(...).field2(...).build()
Проще пояснить на примере.
/** * Пример модели-задачи для демонстрации работы с конструторами в условиях lombok */ public class Task { // ..... // для того, чтобы Spring Data Jdbc - создал сущность в условиях нескольких конструкторов (для других ORM можно найти аналог) @PersistenceConstructor // builder для тестов. расчет на то, что в продакшен коде писать Task.testEntity @Builder(builderMethodName = "testEntity", builderClassName = "TaskTestBuilder") public Task(UUID id, int version/* ... */) { this.id = id; this.version = version; // ... } /** * Создание задачи * @param name Описание задачи * @param generator Генератор номеров задач */ @Builder(builderMethodName = "create", builderClassName = "TaskCreateBuilder") public Task(String name, NumberGenerator generator) { this.id = UUID.randomUUID(); // ... } /** * Копирующий конструктор * @param source Источник данных * @param generator Генератор номеров задач */ @Builder(builderMethodName = "copy", builderClassName = "TaskCopyBuilder") public Task(Task source, NumberGenerator generator) { this.id = UUID.randomUUID(); this.version = version; // ... } }
В указанном примере рассматривается некая модель "задача". У нее есть бизнес смысл. Логика работы. Как видно из примера, эти конструкторы чуть умнее, они не просто принимают поля, но и функции, из которых при необходимости могут достать какие-то специфичные данные. За функциями соответственно скрываются сервисы. Видится, что такой подход более успешен, чем отдельный класс-фабрика, либо метод в сервисе, где вызывается такой же builder, только с чистыми данными. В общем, получается компактно и сильно меньше кода.
И сразу поругаю lombok
Builder
, прописывать каждый разbuilderClassName
- напрягает, хотелось бы его каждый раз не писать. -
Выключаю префиксы в getter-ах.
На самом деле - это чистая эстетика. Но если в record-ах на получение данных отошли от префикса get, давайте и в других местах делать так же.
# lombok.config # выключить префиксы у get/set lombok.accessors.fluent = true
-
Аккуратно используем @RequiredArgsConstructor
Нужно следить за количеством final полей в классе и не допускать слишком большого количества. Раньше, если полей в конструкторе становилось слишком много - это сразу триггер, что делается что-то не так. Данная функциональность с одной стороны убирает необходимость генерить тупой код, с другой стороны уходит важный триггер. Но разработчики ленивы, поэтому - мы используем эту аннотацию.
-
@NonNull - выключить NPEа
# lombok.config # выкидывать осознано NPE кажется не лучшей затеей, чаще ожидаешь какой-то неожиданности в синтаксисе или что-то в этом роде lombok.nonNull.exceptionType = IllegalArgumentException
Остальные возможности lombok не столь деструктивны при их активном использовании.
Lombok - та ещё задумка. Особенно меня пугает код от начинающих разработчиков, которые ещё не достаточно окрепли в системном мышлении. Будь моя воля, я бы и дальше генерил код в IDEA вместо использования lombok, однако я не один на проекте. Поэтому мы сейчас живем так, как я описал выше, т.е. в некоторой системе правил, которая не позволяет разработчикам сильно запутать проект.
UPD: добавил уточнения - по комментариям.
Если старая java, то
@Value
тоже не плохой выбор, но не@Data.
Если вы пишете в процедурном стиле, можете использовать свои setter-ы, но аккуратно. Если вы пишите с использованием принципов ООП, но без setter-ов вам не обойтись, вам стоит пересмотреть свои подходы.
Мысль вслух, не хотел её озвучивать, но похоже придется - lombok - это не просто библиотечка по генерации boilerplate-кода. Фактически это пиратская надстройка над языком, которая стала популярна. Стал ли от этого язык лучше? ИХМО, появилось больше возможностей выстрелить себе в ногу более элегантно и удобно.
Комментарии (23)
mello1984
03.05.2024 16:07+11Главный момент в Ломбоке, с которым стоит быть осторожнее - equals и hashcode для entity. Тут надо действительно быть аккуратным.
Все остальное в статье абсолютная вкусовщина, которая подана как откровение.
При этом похоже автор не понял, что ломбок - это сокращение генерируемого бойлерплейта (тех же конструкторов, геттеров, сеттеров, тустрингов и прочего). Если у вас другой подход (с иной логикой, которая подразумевает умные объекты, инкапсулирующие свое поведение) - ломбок не про это.
В своей части он прекрасен.
png Автор
03.05.2024 16:07>>equals и hashcode
ну это элементарно, даже не стоит внимания. Хотя я ещё встречаю разрабов, которые не могут назвать контракт equals-hashcode на собесах.
>>ломбок - это сокращение генерируемого бойлерплейта
Только ли? Если убрать механику реализации за скобки, мы получаем новый синтаксический сахар в язык. Именно так воспринимают его многие - как часть языка, которую можно включить и будет типа удобно. Посмотрите-почитайте авторов котлина в той части, где они бьются за то, чтобы не вводить в язык новый синтаксический сахар. Посмотрите их аргументы почему новые фичи вводить не надо. Если смотреть через эту призму, там не всё так прекрасно, как вы говорите.
cashby
just so that you know - this library first got released almost 15 years ago. Who are those "conservative developers" then?