Статьи

Обработка повторного кода автоматически

В этой статье я опишу, как вы можете использовать генератор Java :: Geci Repeated для преодоления недостатка языка Java, который не может быть примитивным. Пример представляет собой предлагаемое расширение библиотеки Apache Commons Lang.

Вступление

Когда вы копируете и вставляете код, вы делаете что-то не так. По крайней мере, это восприятие. Вы должны создать свою структуру кода более обобщенной, чтобы вы могли использовать разные параметры вместо одного и того же кода много раз.

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

Это слишком абстрактно. Давайте рассмотрим конкретный пример и то, как мы можем управлять им, используя генератор повторяющихся исходных кодов, который работает в среде Java :: Geci.

Эта проблема

Класс org.apache.commons.lang3.Functions в библиотеке Apache Commons Lang определяет внутренний интерфейс FailableFunction . Это общий интерфейс, определенный как

01
02
03
04
05
06
07
08
09
10
@FunctionalInterface
    public interface FailableFunction<I, O, T extends Throwable> {
        /**
         * Apply the function.
         * @param pInput the input for the function
         * @return the result of the function
         * @throws T if the function fails
         */
        O apply(I pInput) throws T;
    }

По сути, это то же самое, что и Function<I,O> , которая преобразует I в O но, поскольку интерфейс неисправен, он также может вызвать исключение типа T

Новая потребность в том, чтобы иметь

1
public interface Failable<I>Function<O, T extends Throwable>

itnerfaces для каждого <I> значения примитива. Проблема в том, что дженерики не могут быть примитивными (пока) в Java, и поэтому мы должны отделить интерфейсы для каждого примитивного типа, как

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@FunctionalInterface
    public interface FailableCharFunction<O, T extends Throwable> {
        O apply(char pInput) throws T;
    }
    @FunctionalInterface
    public interface FailableByteFunction<O, T extends Throwable> {
        O apply(byte pInput) throws T;
    }
    @FunctionalInterface
    public interface FailableShortFunction<O, T extends Throwable> {
        O apply(short pInput) throws T;
    }
    @FunctionalInterface
    public interface FailableIntFunction<O, T extends Throwable> {
        O apply(int pInput) throws T;
    }
... and so on ...

Это много очень похожих методов, которые можно легко описать с помощью шаблона, а затем сгенерировать с помощью некоторого инструмента генерации кода.

Обработка шаблонов с использованием Java :: Geci

Инфраструктура Java :: Geci поставляется с множеством готовых генераторов. Одним из них является мощный генератор Repeated импульсов, который предназначен именно для этой цели. Если есть код, который должен быть повторен с возможными параметрами, тогда вы можете определить шаблон, значения и Repeated сгенерируют код, разрешающий параметры шаблона.

Добавление зависимости в POM

Первое, что нам нужно сделать, это добавить зависимости Java :: Geci в файл pom.xml . Поскольку Apache Commons Language все еще основан на Java 8, мы должны использовать бэкпорт Java 8 Java :: Geci 1.2.0:

1
2
3
4
5
6
<dependency>
      <groupId>com.javax1.geci</groupId>
      <artifactId>javageci-core</artifactId>
      <version>1.2.0</version>
      <scope>test</scope>
    </dependency>

Обратите внимание, что область зависимости является test . Генератор Repeated удобно использовать без каких-либо аннотаций Geci, которые остаются в байтовом коде и, таким образом, являются зависимостями времени компиляции. Фактически, все генераторы могут использоваться без аннотаций, таким образом, без каких-либо зависимостей компиляции, которые были бы дополнительной зависимостью для производства. В случае Repeated это даже легко сделать.

Юнит тест для запуска генератора

Второе, что нам нужно сделать, — это создать модульный тест, который будет запускать генератор. Генераторы Java :: Geci работают на этапе модульного тестирования, поэтому они могут получить доступ к уже скомпилированному коду, используя отражение, а также фактический исходный код. Если какой-либо сгенерированный код отличается от того, который уже был в исходном файле, тест не пройден, и процесс сборки должен быть выполнен снова. Поскольку генераторы являются (должны быть) идемпотентными, тест не должен проваливаться во второй раз.

Как я понимаю, этот рабочий процесс, к сожалению, влияет на поведение разработчика. Запустить тест / не удалось, запустить снова! Это плохой цикл. Иногда я ловлю себя на повторном выполнении модульных тестов, когда это не был генератор кода, который не удался. Однако так работает Java :: Geci.

Есть статьи о рабочем процессе Java :: Geci

поэтому я не буду повторять здесь общую архитектуру и как идет ее рабочий процесс.

Модульные тесты будут следующими:

01
02
03
04
05
06
07
08
09
10
11
12
13
@Test
    void generatePrimitiveFailables() throws Exception {
        final Geci geci = new Geci();
        Assertions.assertFalse(geci.source(Source.maven().mainSource())
                .only("Functions")
                .register(Repeated.builder()
                    .values("char,byte,short,int,long,float,double,boolean")
                    .selector("repeated")
                    .define((ctx, s) -> ctx.segment().param("Value", CaseTools.ucase(s)))
                    .build())
                .generate(),
            geci.failed());
    }

Вызовы source() , register() и only() настраивают структуру. Эта конфигурация указывает платформе использовать исходные файлы, которые находятся в главном каталоге Java src проекта, и использовать только имена файлов "Functions" . Вызов register() регистрирует экземпляр Repeated generator непосредственно перед тем, как мы вызываем generate() который запускает генерацию кода.

Сам экземпляр генератора создается с помощью встроенного компоновщика, который позволяет нам настраивать генератор. В этом случае вызов values() определяет разделенный запятыми список значений, с которыми мы хотим повторить шаблон (определенный позже в коде в комментарии). Вызов selector() определяет идентификатор для этого кода повторного кода. Один исходный файл может содержать несколько шаблонов. Каждый шаблон может быть обработан с другим списком значений, и результат будет вставлен в разные выходные сегменты в исходный файл. В этом случае существует только один такой шаблон генерации кода, тем не менее, он должен быть идентифицирован по имени, и это имя также должно использоваться в разделе editor-fold который является заполнителем для сгенерированного кода.

Фактическое использование имени генератора имеет два эффекта. Во-первых, он идентифицирует сегмент сгиба редактора и шаблон. Другой заключается в том, что фреймворк увидит сегмент редактора с этим идентификатором и распознает, что этот исходный файл требует внимания этого генератора. Другой возможностью было бы добавить @Repeated или @Geci("repeated") к классу.

Если бы идентификатор был чем-то другим и не repeated то генератор Repeated не затронул бы исходный код, или нам понадобился бы другой сегмент, идентифицированный как repeated , который фактически не использовался бы, кроме как запускать генератор.

Вызов define() определяет BiConsumer который получает ссылку на контекст и фактическое значение. В этом случае BiConsumer вычисляет заглавное значение и помещает его в фактический набор параметров сегмента, связанный с именем Value . Фактическое значение связано со value имени по умолчанию, и BiConsumer переданный методу define() может определять и регистрировать другие параметры. В этом случае это добавит новые значения как

01
02
03
04
05
06
07
08
09
10
value       Value
 
char    --> Char
byte    --> Byte
short   --> Short
int     --> Int
long    --> Long
float   --> Float
double  --> Double
boolean --> Boolean

Исходный код

Третье — подготовить шаблон и выходной сегмент в исходном файле.

Подготовка выходного сегмента предельно проста. Это только редактор сгиба:

1
2
//<editor-fold id="repeated">
    //</editor-fold>

Сгенерированный код будет автоматически вставлен между двумя строками, а редакторы (Eclipse, IntelliJ или NetBeans) позволят вам закрыть сгиб. Вы не хотите редактировать этот код: он генерируется.

Шаблон будет выглядеть следующим образом:

1
2
3
4
5
6
/* TEMPLATE repeated
    @FunctionalInterface
    public interface Failable{{Value}}Function<O, T extends Throwable> {
        O apply({{value}} pInput) throws T;
    }
    */

Генератор кода находит начало шаблона, ищет строки, соответствующие формату /* TEMPLATE name , и собирает последовательные строки до конца комментария.

Шаблон использует формат заполнителя шаблона усы, а именно имя значений, заключенных в двойные скобки. Двойные скобки редки на Яве.

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

Резюме и вынос

Наиболее важный вывод и ПРЕДУПРЕЖДЕНИЕ: генерация исходного кода — это инструмент, направленный на исправление недостатков языка программирования. Не используйте генерации кода для исправления недостатка, который связан не с языком, а с вашим опытом, навыками или знанием языка. Простой способ генерации кода не является оправданием для создания ненужного избыточного кода.

Другим выводом является то, что этот генератор чрезвычайно легко использовать в Java. Функциональность сопоставима с препроцессором C, которого нет в Java, и навсегда. Используйте его, когда это необходимо. Даже несмотря на то, что настройка зависимостей и модульное тестирование могут потребовать незначительных затрат, впоследствии ремонтопригодность обычно окупается.

Опубликовано на Java Code Geeks с разрешения Питера Верхаса, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Автоматическая обработка повторного кода

Мнения, высказанные участниками Java Code Geeks, являются их собственными.