company_banner

Lombok, sources.jar и удобный дебаг

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



    Кстати, если вы пишете на Java и по какой-то причине всё ещё не используете Lombok в своём проекте, то я рекомендую познакомиться со статьями на Хабре (раз и два). Уверен, вам понравится!

    Проблема


    Проект, над которым мы работаем, состоит из нескольких модулей. Часть из них (назовём их условно backend) при выпуске релиза пакуется в архив (поставку), загружается в репозиторий и впоследствии деплоится на серверы приложений. Другая часть — т.н. клиентский модуль — публикуется в репозиторий в виде набора артефактов, содержащего в т.ч. sources.jar и javadoc.jar. Lombok мы используем во всех частях, а собирается всё это Maven'ом.

    Некоторое время назад один из потребителей нашего сервиса обратился с проблемой — он пытался дебажить наш модуль, но не мог этого сделать, т.к. в sources.jar отсутствовали методы (и даже классы), в которых он хотел бы поставить breakpoint. Мы в нашей команде считаем, что попытка самостоятельно выявить и решить проблему, вместо того, чтобы бездумно заводить дефект — поступок достойного мужа, который нужно поощрять! :-) Поэтому было принято решение привести sources.jar в соответствие с байткодом.

    Пример


    Давайте представим себе, что у нас есть простое приложение, состоящее из двух классов:

    SomePojo.java
    package com.github.monosoul.lombok.sourcesjar;
    
    import lombok.Builder;
    import lombok.Value;
    
    @Value
    @Builder(toBuilder = true)
    class SomePojo {
    
        /**
         * Some string field
         */
        String someStringField;
        /**
         * Another string field
         */
        String anotherStringField;
    }
    


    Main.java
    package com.github.monosoul.lombok.sourcesjar;
    
    import lombok.val;
    
    public final class Main {
    
        public static void main(String[] args) {
            if (args.length != 2) {
                throw new IllegalArgumentException("Wrong arguments!");
            }
    
            val pojo = SomePojo.builder()
                               .someStringField(args[0])
                               .anotherStringField(args[1])
                               .build();
    
            System.out.println(pojo);
        }
    }
    


    И собирается наше приложение с помощью Maven'а:

    pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <artifactId>lombok-sourcesjar</artifactId>
      <groupId>com.github.monosoul</groupId>
      <version>1.0.0</version>
      <packaging>jar</packaging>
    
      <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.2</version>
          <scope>provided</scope>
        </dependency>
      </dependencies>
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.1.1</version>
            <configuration>
              <archive>
                <manifest>
                  <mainClass>com.github.monosoul.lombok.sourcesjar.Main</mainClass>
                </manifest>
              </archive>
            </configuration>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.0.1</version>
            <executions>
              <execution>
                <id>attach-sources</id>
                <goals>
                  <goal>jar</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    
    </project>
    


    Если скомпилировать этот проект (mvn compile), а затем сдекомпилировать получившийся байткод, то класс SomePojo будет выглядеть так:

    SomePojo.class
    package com.github.monosoul.lombok.sourcesjar;
    
    final class SomePojo {
        private final String someStringField;
        private final String anotherStringField;
    
        SomePojo(String someStringField, String anotherStringField) {
            this.someStringField = someStringField;
            this.anotherStringField = anotherStringField;
        }
    
        public static SomePojo.SomePojoBuilder builder() {
            return new SomePojo.SomePojoBuilder();
        }
    
        public SomePojo.SomePojoBuilder toBuilder() {
            return (new SomePojo.SomePojoBuilder()).someStringField(this.someStringField).anotherStringField(this.anotherStringField);
        }
    
        public String getSomeStringField() {
            return this.someStringField;
        }
    
        public String getAnotherStringField() {
            return this.anotherStringField;
        }
    
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            } else if (!(o instanceof SomePojo)) {
                return false;
            } else {
                SomePojo other = (SomePojo)o;
                Object this$someStringField = this.getSomeStringField();
                Object other$someStringField = other.getSomeStringField();
                if (this$someStringField == null) {
                    if (other$someStringField != null) {
                        return false;
                    }
                } else if (!this$someStringField.equals(other$someStringField)) {
                    return false;
                }
    
                Object this$anotherStringField = this.getAnotherStringField();
                Object other$anotherStringField = other.getAnotherStringField();
                if (this$anotherStringField == null) {
                    if (other$anotherStringField != null) {
                        return false;
                    }
                } else if (!this$anotherStringField.equals(other$anotherStringField)) {
                    return false;
                }
    
                return true;
            }
        }
    
        public int hashCode() {
            int PRIME = true;
            int result = 1;
            Object $someStringField = this.getSomeStringField();
            int result = result * 59 + ($someStringField == null ? 43 : $someStringField.hashCode());
            Object $anotherStringField = this.getAnotherStringField();
            result = result * 59 + ($anotherStringField == null ? 43 : $anotherStringField.hashCode());
            return result;
        }
    
        public String toString() {
            return "SomePojo(someStringField=" + this.getSomeStringField() + ", anotherStringField=" + this.getAnotherStringField() + ")";
        }
    
        public static class SomePojoBuilder {
            private String someStringField;
            private String anotherStringField;
    
            SomePojoBuilder() {
            }
    
            public SomePojo.SomePojoBuilder someStringField(String someStringField) {
                this.someStringField = someStringField;
                return this;
            }
    
            public SomePojo.SomePojoBuilder anotherStringField(String anotherStringField) {
                this.anotherStringField = anotherStringField;
                return this;
            }
    
            public SomePojo build() {
                return new SomePojo(this.someStringField, this.anotherStringField);
            }
    
            public String toString() {
                return "SomePojo.SomePojoBuilder(someStringField=" + this.someStringField + ", anotherStringField=" + this.anotherStringField + ")";
            }
        }
    }
    


    Довольно сильно отличается от того, что попадёт в наш sources.jar, не правда ли? ;) Как видите, если бы вы подключили исходники для дебага SomePojo и захотели поставить breakpoint, например, в конструкторе, то вы бы столкнулись с проблемой — breakpoint ставить некуда, а класса SomePojoBuilder там вообще нет.

    Что с этим делать?


    Как это часто бывает — у этой проблемы есть несколько способов решения. Давайте рассмотрим каждый из них.

    Не использовать Lombok


    Когда мы столкнулись с этой проблемой впервые — речь шла о модуле, в котором была всего пара классов, использующих Lombok. Отказываться от него, конечно, не хотелось, поэтому я сразу подумал о том, чтоб делать delombok. Поисследовав этот вопрос, я нашёл несколько странных решений с использованием Lombok-плагина для Maven'а — lombok-maven-plugin. В одном из них предлагали, например, держать исходники, в которых используется Lombok, в отдельной директории, для которой будет выполняться delombok, и сгенерированные исходники будут попадать в generated-sources, откуда уже будет компилироваться и попадать в sources.jar. Вариант это, наверное, рабочий но в этом случае в IDE не будет работать подсветка синтаксиса в оригинальных исходниках, т.к. каталог с ними не будет считаться директорией с исходниками. Такой вариант меня не устраивал, и, поскольку цена отказа от Lombok'а в этом модуле была невелика, было принято решение не тратить на это время, отключить Lombok и просто сгенерировать нужные методы через IDE.

    В целом, мне кажется, что такой вариант имеет право на жизнь, но только если классов, использующих Lombok действительно мало и они редко меняются.


    Delombok плагин + сборка sources.jar с помощью Ant


    Спустя некоторое время пришлось вернуться к этой проблеме снова, когда речь шла уже о модуле, в котором Lombok использовался гораздо более интенсивно. Вернувшись снова к изучению этой проблемы, я наткнулся на вопрос на stackoverflow, где предлагалось выполнять для исходников delombok, а затем с помощью задачи в Ant генерировать sources.jar.
    Тут нужно сделать отступление о том, почему генерировать sources.jar нужно именно с помощью Ant'а, а не с помощью Source-плагина (maven-source-plugin). Дело в том, что для этого плагина нельзя сконфигурировать директорию с исходниками. Он всегда будет использовать содержимое свойства sourceDirectory проекта.

    Итак, в случае с нашим примером, pom.xml станет выглядеть так:

    pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <artifactId>lombok-sourcesjar</artifactId>
      <groupId>com.github.monosoul</groupId>
      <version>1.0.0</version>
      <packaging>jar</packaging>
    
      <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <lombok.version>1.18.2</lombok.version>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>${lombok.version}</version>
          <scope>provided</scope>
        </dependency>
      </dependencies>
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.1.1</version>
            <configuration>
              <archive>
                <manifest>
                  <mainClass>com.github.monosoul.lombok.sourcesjar.Main</mainClass>
                </manifest>
              </archive>
            </configuration>
          </plugin>
    
          <plugin>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok-maven-plugin</artifactId>
            <version>${lombok.version}.0</version>
            <executions>
              <execution>
                <phase>generate-sources</phase>
                <goals>
                  <goal>delombok</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <sourceDirectory>src/main/java</sourceDirectory>
              <outputDirectory>${project.build.directory}/delombok</outputDirectory>
              <addOutputDirectory>false</addOutputDirectory>
              <encoding>UTF-8</encoding>
              <formatPreferences>
                <generateDelombokComment>skip</generateDelombokComment>
              </formatPreferences>
            </configuration>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <version>3.1.0</version>
            <executions>
              <execution>
                <id>copy-to-lombok-build</id>
                <phase>process-resources</phase>
                <goals>
                  <goal>copy-resources</goal>
                </goals>
                <configuration>
                  <resources>
                    <resource>
                      <directory>${project.basedir}/src/main/resources</directory>
                    </resource>
                  </resources>
                  <outputDirectory>${project.build.directory}/delombok</outputDirectory>
                </configuration>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-antrun-plugin</artifactId>
            <version>1.8</version>
            <executions>
              <execution>
                <id>generate-delomboked-sources-jar</id>
                <phase>package</phase>
                <goals>
                  <goal>run</goal>
                </goals>
                <configuration>
                  <target>
                    <jar destfile="${project.build.directory}/${project.build.finalName}-sources.jar"
                       basedir="${project.build.directory}/delombok"/>
                  </target>
                </configuration>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-install-plugin</artifactId>
            <version>2.5.2</version>
            <executions>
              <execution>
                <id>install-source-jar</id>
                <goals>
                  <goal>install-file</goal>
                </goals>
                <phase>install</phase>
                <configuration>
                  <file>${project.build.directory}/${project.build.finalName}-sources.jar</file>
                  <classifier>sources</classifier>
                  <generatePom>true</generatePom>
                  <pomFile>${project.basedir}/pom.xml</pomFile>
                </configuration>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-deploy-plugin</artifactId>
            <version>3.0.0-M1</version>
            <executions>
              <execution>
                <id>deploy-source-jar</id>
                <goals>
                  <goal>deploy-file</goal>
                </goals>
                <phase>deploy</phase>
                <configuration>
                  <file>${project.build.directory}/${project.build.finalName}-sources.jar</file>
                  <classifier>sources</classifier>
                  <generatePom>true</generatePom>
                  <pomFile>${project.basedir}/pom.xml</pomFile>
                  <repositoryId>someRepoId</repositoryId>
                  <url>some://repo.url</url>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    
    </project>
    


    Как видите, конфигурация очень сильно разрослась, и в ней есть далеко не только lombok-maven-plugin и maven-antrun-plugin. Почему так произошло? Дело в том, что поскольку sources.jar мы теперь собираем Ant'ом, то Maven ничего об этом артефакте не знает. И нам нужно явно указать ему, как этот артефакт устанавливать, как его деплоить и как паковать в него ресурсы.

    Кроме того, я обнаружил, что при выполнении delombok'а по умолчанию Lombok добавляет в шапку сгенерированных файлов комментарий. При этом формат генерирумеых файлов управляется не с помощью опций в файле lombok.config, а с помощью опций плагина. Список этих опций оказалось непросто найти. Можно было, конечно, вызвать jar-ник Lombok'а с ключами delombok и --help, но я слишком ленивый для этого ж программист, поэтому я нашёл их в исходниках на гитхабе.

    Но ни объём конфигурации, ни её особенности не идут ни в какое сравнение с главным недостатком этого способа. Он не решает проблему. Байткод компилируется из одних исходников, а в sources.jar попадают другие. И несмотря на то, что delombok выполняется тем же самым Lombok'ом, между байткодом и сгенерированными исходниками всё равно будут отличия, т.е. для дебага они по прежнему непригодны. Мягко говоря, я расстроился, когда понял это.


    Delombok плагин + профиль в maven


    Так что же делать? У меня был sources.jar с «правильными» исходниками, но они всё равно отличались от байткода. В принципе проблему могла бы решить компиляция из исходников, сгенерированных в результате delombok'а. Но проблема в том, что maven-compiler-plugin'у нельзя указать путь до исходников. Он всегда использует исходники, указанные в sourceDirectory проекта, как и maven-source-plugin. Можно было бы указать там каталог, в который генерируются delomboked исходники, но в этом случае при импорте проекта в IDE, каталог с реальными исходниками не будет считаться таковым и для файлов в нём не будет работать подсветка синтаксиса и другие фичи. Такой вариант меня тоже не устраивал.

    Можно использовать профили! Создать профиль, который бы использовался только при сборке проекта и в котором бы подменялось значение sourceDirectory! Но есть нюанс. Тег sourceDirectory можно объявить только внутри тега build в корне проекта.

    К счастью, для этой проблемы есть обходной путь. Можно объявить свойство, которое будет подставляться в тег sourceDirectory, а в профиле менять значение этого свойства!

    В этом случае конфигурация проекта будет выглядеть так:

    pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <artifactId>lombok-sourcesjar</artifactId>
      <groupId>com.github.monosoul</groupId>
      <version>1.0.0</version>
      <packaging>jar</packaging>
    
      <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    
        <lombok.version>1.18.2</lombok.version>
    
        <origSourceDir>${project.basedir}/src/main/java</origSourceDir>
        <sourceDir>${origSourceDir}</sourceDir>
        <delombokedSourceDir>${project.build.directory}/delombok</delombokedSourceDir>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>${lombok.version}</version>
          <scope>provided</scope>
        </dependency>
      </dependencies>
    
      <profiles>
        <profile>
          <id>build</id>
          <properties>
            <sourceDir>${delombokedSourceDir}</sourceDir>
          </properties>
        </profile>
      </profiles>
    
      <build>
        <sourceDirectory>${sourceDir}</sourceDirectory>
        <plugins>
          <plugin>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok-maven-plugin</artifactId>
            <version>${lombok.version}.0</version>
            <executions>
              <execution>
                <phase>generate-sources</phase>
                <goals>
                  <goal>delombok</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <sourceDirectory>${origSourceDir}</sourceDirectory>
              <outputDirectory>${delombokedSourceDir}</outputDirectory>
              <addOutputDirectory>false</addOutputDirectory>
              <encoding>UTF-8</encoding>
              <formatPreferences>
                <generateDelombokComment>skip</generateDelombokComment>
                <javaLangAsFQN>skip</javaLangAsFQN>
              </formatPreferences>
            </configuration>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.1.1</version>
            <configuration>
              <archive>
                <manifest>
                  <mainClass>com.github.monosoul.lombok.sourcesjar.Main</mainClass>
                </manifest>
              </archive>
            </configuration>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.0.1</version>
            <executions>
              <execution>
                <id>attach-sources</id>
                <goals>
                  <goal>jar</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    
    </project>
    


    Работает это следующим образом. В свойство origSourceDir мы подставляем путь до каталога с оригинальными исходниками, а в свойство sourceDir по умолчанию подставляем значение из origSourceDir. В свойстве delombokedSourceDir мы указываем путь до исходников, сгенерированных delombok'ом. Таким образом, при импорте проекта в IDE используется каталог из origSourceDir, а при сборке проекта, если указать профиль build, будет использован каталог delombokedSourceDir.

    В результате мы получим байткод, скомпилированный из тех же исходников, которые попадут в sources.jar, т.е. дебаг наконец-то будет работать. При этом нам не нужно конфигурировать установку и деплой артефакта с исходниками, поскольку мы используем для генерации артефакта плагин maven-source-plugin. Правда, магия с переменными может запутать незнакомого с нюансами Maven'а человека.

    А ещё можно в lombok.config добавить опцию lombok.addJavaxGeneratedAnnotation = true, тогда в сгенерированных исходниках над сгенерированным кодом будет стоять аннотация @javax.annotation.Generated("lombok"), что поможет избежать вопросов типа «Почему ваш код выглядит так странно?!». :)


    Использовать Gradle


    Думаю, если вы уже знакомы с Gradle, то не стоит объяснять все его преимущества перед Maven. Если же вы ещё не знакомы с ним, то на хабре для этого есть целый Хаб. Отличный повод заглянуть в него! :)
    Вообще, когда я подумал об использовании Gradle, то я ожидал, что сделать в нём то, что мне нужно, будет гораздо проще, поскольку я знал, что уж в нём-то я без проблем смогу указать из чего собирать sources.jar и что компилировать в байткод. Проблема поджидала меня там, где я ожидал меньше всего — для Gradle нет работающего delombok плагина.

    Есть этот плагин, но похоже, что в нём нельзя указать опции для форматирования delomboked-исходников, что мне не подходило.

    Есть ещё один плагин, он генерирует текстовый файл из переданных ему опций, а потом передаёт его в качестве аргумента lombok.jar. Мне не удалось заставить его складывать сгенерированные исходники в нужный каталог, похоже это связано с тем, что путь в текстовом файле с аргументами не берётся в кавычки и не экранируется должным образом. Возможно позже я сделаю пулл реквест автору плагина с предложением исправления.

    В итоге я решил пойти другим путём и просто описал задачу с вызовом Ant для выполнения delombok'а, в Lombok'е как раз есть Ant task для этого, и выглядит это вполне неплохо:

    build.gradle.kts
    group = "com.github.monosoul"
    version = "1.0.0"
    
    plugins {
        java
    }
    
    java {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    
    dependencies {
        val lombokDependency = "org.projectlombok:lombok:1.18.2"
    
        annotationProcessor(lombokDependency)
        compileOnly(lombokDependency)
    }
    
    repositories {
        mavenCentral()
    }
    
    tasks {
        "jar"(Jar::class) {
            manifest {
                attributes(
                        Pair("Main-Class", "com.github.monosoul.lombok.sourcesjar.Main")
                )
            }
        }
    
        val delombok by creating {
            group = "lombok"
    
            val delombokTarget by extra { File(buildDir, "delomboked") }
            
            doLast({
                ant.withGroovyBuilder {
                    "taskdef"(
                            "name" to "delombok",
                            "classname" to "lombok.delombok.ant.Tasks\$Delombok",
                            "classpath" to sourceSets.main.get().compileClasspath.asPath)
                    "mkdir"("dir" to delombokTarget)
                    "delombok"(
                            "verbose" to false,
                            "encoding" to "UTF-8",
                            "to" to delombokTarget,
                            "from" to sourceSets.main.get().java.srcDirs.first().absolutePath
                    ) {
                        "format"("value" to "generateDelombokComment:skip")
                        "format"("value" to "generated:generate")
                        "format"("value" to "javaLangAsFQN:skip")
                    }
                }
            })
        }
    
        register<Jar>("sourcesJar") {
            dependsOn(delombok)
    
            val delombokTarget: File by delombok.extra
            from(delombokTarget)
            archiveClassifier.set("sources")
        }
    
        withType(JavaCompile::class) {
            dependsOn(delombok)
    
            val delombokTarget: File by delombok.extra
            source = fileTree(delombokTarget)
        }
    }
    


    По результату этот вариант эквивалентен предыдущему.


    Выводы


    Довольно тривиальная, по сути, задача в итоге оказалась чередой попыток найти обходные пути вокруг странных решений авторов Maven. Как по мне — скрипт Gradle, на фоне получившихся конфигов Maven'а, выглядит гораздо более очевидно и логично. Впрочем, может быть мне просто не удалось найти решение лучше? Расскажите в комментариях, решали ли вы похожую задачу, и если решали, то каким образом.

    Спасибо за чтение!

    Исходники
    Сбербанк
    160,01
    Компания
    Поделиться публикацией

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

      0
      Предполагаю, что на написание статьи ушло больше времени, чем нужно было на генерацию конструкторов и сеттерев/геттеров в IDE.
        +3
        Не без этого. :) Но лично мне Lombok нравится тем, что он позволяет фокусировать внимание только на том коде, который делает что-то важное. Это удобно тем, что если по задаче был рефакторинг, например, то diff, который нужно ревьювить будет меньше. Долго запрягаем, но быстро ездим.
          0
          После пары лет работы с ломбоком у меня выработалось к нему сильное привыкание. Недавно, буквально, хныкал, когда добавлял в легаси-класс несколько полей, а потом добавлял их же в билдер, а потом корректировал имена полей, а потом корректировал их в билдере, а потом нужно было изменить тип, и следом изменить его в билдере.
          Конечно все эти мелкие операции заняли у меня меньше времени, чем заняло бы написание статьи, но и удовольствия это мне не принесло. Хочется тратить свое время на что-то более творческое.
          +2
          Думаю, если вы уже знакомы с Gradle, то не стоит объяснять все его преимущества перед Maven.

          И все же! В чем преимущества?
            0

            Хороший вопрос! :) Думаю, это станет темой для следующего поста!

            +1
            Очень своевременная статья. Думаю теперь, после прочтения я точно знаю, что в нашем проекте этого не будет.
              0

              Сами создали проблему, сами её решили, виртуозно исполнив танец на костылях.


              Некоторое время назад один из потребителей нашего сервиса обратился с проблемой — он пытался дебажить наш модуль, но не мог этого сделать, т.к. в sources.jar отсутствовали методы (и даже классы), в которых он хотел бы поставить breakpoint.

              Что помешало убрать бесполезный sources.jar из проекта и невозбранно ставить какие душе угодно точки останова в декомпилированном коде?

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

              Самое читаемое