Когда я впервые увидел - lombok, у меня возникло, дикое сопротивление. Было очевидное ощущение, что что-то не так. Я думаю, у многих консервативных разработчиков возникло такое же ощущение. Однако, lombok популярен. Люди его любят, люди его используют. А значит, есть и будут появляться проекты с ним. А значит нам с этим всем придется как-то жить.

Мои претензии к lombok

Кратко:

  1. провоцирует менять доменную модель откуда вздумается и как вздумается при помощи setter-ов.

  2. провоцирует писать бизнесовую логику где придется, получая необходимые данные из getter-ов.

  3. сгенеренные builder-ы становятся тупыми, а могли бы быть чуть удобней.

  4. он конфликтует с spring data jdbc в плане создания объектов из-за конструкторов.

  5. есть проблемы с иерархией наследования.

  6. откровенно не красивый код с ёлками из аннотаций в начале класса.

  7. есть недочеты в API. например, аннотация Builder на классе, которая автоматом создает конструктор. Вот ни разу не ожидаешь подвоха.

  8. слабая поддержка в плагине(даже на момент сейчас у меня к плагину есть вопросы по навигации, я уже молчу про времена, пару лет назад, когда выходила новая IDEA и плагин ломался).

  9. Сюда можно ещё холивары добавить, но не буду, мое отношение и так понятно.

Как мы его готовим - best practics

На своих проектах я постарался выстроить некую систему правил использования lombok.

  1. Аннотация @Setter - запрещена (если вы без них вообще никак, то вещайте на конкретные поля и пользуйте аккуратно, соблюдая слои приложения).

    Существует антипаттерн - цепочка setter-ов. Для изменения данных в объекте пиши свой метод. Описывай его бизнес смысл в имени метода и в javaDoc. Фиксируй логику метода в тестах. Проверяй состояние сущности в методе. И так далее ...

    С этой аннотацией и её аналогами цепочки setter-ов на проекте начинают плодиться с устрашающей скоростью. Не успеваешь выбраковывать PullRequest-ы.

    UPD: стоит оговориться, сейчас набежали в комментарии и напомнили, что существует такой подход к разработке ПО, как transaction script. Подход достаточно старый, но видимо всё ещё популярный в определенных кругах. Я не против самого подхода, есть задачи, где так действительно проще, но возникает вопрос - setter-ы, без них никак, а как иначе изменить модель структуру с данными из сервиса.

    Если бы перед мной стояла похожая задача, я бы объекты всё-равно делал бы умней, а ту логику которую можно вынести в модель, всё же вынес. Во-первых убрал бы дикий копипаст на однотипные изменения(и получение данных - к getter-ам это тоже относится). Во-вторых, у нас всё-таки ООП язык, процедурный стиль разработки сюда не к месту.

    Если вы на проекте используете domain model подход(или думаете, что вы его используете), то вам setter-ы не нужны.

    С учетом замечаний добавил оговорку курсивом.

  2. Аннотация @Getter - разрешена только на поле, на классе нельзя.

    Это попытка уйти от размазывания логики работы с данными в 100-500 сервисах. Начнем с того, что это не ОПП, когда чужой класс начинает лезть в модель и разбираться в её структуре. Лучше сделать метод, который вернет необходимые данные этому чужому классу-сервису. Этот метод можно покрыть тестами и тем самым улучшить весь проект, который будет адекватно реагировать на снижение качества данных.

  3. Аннотации @Data/@Value - запрещены.

    В современной java есть record - его достаточно.

    UPD: допустим - у вас древняя java. Тогда для DTO - @Value - ваш выбор, как замена record.

    И уж точно вам не нужна @Data . DTO изменяемой быть не может, а на доменной модели setter-ы - как уже писал выше - нежелательны.

  4. Аннотация @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 - напрягает, хотелось бы его каждый раз не писать.

  5. Выключаю префиксы в getter-ах.

    На самом деле - это чистая эстетика. Но если в record-ах на получение данных отошли от префикса get, давайте и в других местах делать так же.

    # lombok.config
    # выключить префиксы у get/set
    lombok.accessors.fluent = true

  6. Аккуратно используем @RequiredArgsConstructor

    Нужно следить за количеством final полей в классе и не допускать слишком большого количества. Раньше, если полей в конструкторе становилось слишком много - это сразу триггер, что делается что-то не так. Данная функциональность с одной стороны убирает необходимость генерить тупой код, с другой стороны уходит важный триггер. Но разработчики ленивы, поэтому - мы используем эту аннотацию.

  7. @NonNull - выключить NPEа

    # lombok.config
    # выкидывать осознано NPE кажется не лучшей затеей, чаще ожидаешь какой-то неожиданности в синтаксисе или что-то в этом роде
    lombok.nonNull.exceptionType = IllegalArgumentException


    Остальные возможности lombok не столь деструктивны при их активном использовании.

    Lombok - та ещё задумка. Особенно меня пугает код от начинающих разработчиков, которые ещё не достаточно окрепли в системном мышлении. Будь моя воля, я бы и дальше генерил код в IDEA вместо использования lombok, однако я не один на проекте. Поэтому мы сейчас живем так, как я описал выше, т.е. в некоторой системе правил, которая не позволяет разработчикам сильно запутать проект.

    UPD: добавил уточнения - по комментариям.

    1. Если старая java, то @Value тоже не плохой выбор, но не @Data.

    2. Если вы пишете в процедурном стиле, можете использовать свои setter-ы, но аккуратно. Если вы пишите с использованием принципов ООП, но без setter-ов вам не обойтись, вам стоит пересмотреть свои подходы.

    3. Мысль вслух, не хотел её озвучивать, но похоже придется - lombok - это не просто библиотечка по генерации boilerplate-кода. Фактически это пиратская надстройка над языком, которая стала популярна. Стал ли от этого язык лучше? ИХМО, появилось больше возможностей выстрелить себе в ногу более элегантно и удобно.

Комментарии (23)


  1. cashby
    03.05.2024 16:07
    +4

    just so that you know - this library first got released almost 15 years ago. Who are those "conservative developers" then?


  1. mello1984
    03.05.2024 16:07
    +11

    Главный момент в Ломбоке, с которым стоит быть осторожнее - equals и hashcode для entity. Тут надо действительно быть аккуратным.

    Все остальное в статье абсолютная вкусовщина, которая подана как откровение.

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

    В своей части он прекрасен.


    1. png Автор
      03.05.2024 16:07

      >>equals и hashcode

      ну это элементарно, даже не стоит внимания. Хотя я ещё встречаю разрабов, которые не могут назвать контракт equals-hashcode на собесах.

      >>ломбок - это сокращение генерируемого бойлерплейта

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