Позабыты хлопоты,
Остановлен бег,
Вкалывают роботы,
Счастлив человек!
Из фильма «Детство Терминатора» «Приключения Электроника»
Привет, сегодня мы снова поговорим о производительности. О производительности труда разработчиков.
Я расскажу о том, как средствами «Идеи» прокачать этот навык. Надеюсь, мои советы пригодятся вам, замечания и улучшения приветствуются. Поехали!
Обычный разработчик немалую часть своего рабочего дня тратит на рутинные действия. Также до недавнего времени поступал и я. А потом в голове возникло несколько простых и очевидных мыслей:
- нечасто мы пишем действительно что-то новое и необычное
- значительную часть рабочего времени разработчик пишет шаблонный код
- многие используемые нами простые конструкции легко формализуемы, а в своей голове мы выражаем их парой слов
Львиную долю времени я работаю со связкой Спринг Бут/Хибернейт, поэтому в основном кодогенерация и шаблоны касаются именно их, хотя подход универсален и вы легко сможете сделать похожие улучшения для своих проектов.
Театр начинается с вешалки, а приложение на Спринг Буте — с настроек. Обычно они расписываются в файле application.yml
/ application.properties
, но бывает и так, что некоторые бины/конфигурации приходится описывать кодом:
@Configuration @EnableSwagger2 class SwaggerConfig { @Bean Docket documentationApi() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build(); } }
Эта настройка включает Сваггер (полезная в хозяйстве вещь). Подумаем, что можно автоматизировать? Разработчики знают, что над классом настроек ставится @Configuration
. Т.е. можно создать своего рода заготовку — шаблон конфигурационного класса и создавать его лёгким движением руки. «Идея» из коробки даёт пользователю возможность подгонять существующие шаблоны под себя:
Чем мы и воспользуемся:
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end import org.springframework.context.annotation.Configuration; @Configuration class ${NAME} { }
Для опытных разработчиков здесь всё понятно, для новичков объясню:
Строка 1: в код новосозданного класса будет добавлена строчка вроде такой
package com.example.текущий-пакет;
Строка 2: подключение нужной аннотации
Строка 3: тело класс.
Обратите внимание, что переменная ${NAME}
превратиться во всплывающее окно, куда нам нужно будет вписать имя класса.
Итого:
Этот шаблон избавит нас от необходимости руками писать @Configuration
над классом и разруливать импорт. Немного, но нам важно начать и получить немного опыта.
Пустой класс настроек сам по себе мало чего стоит. Давайте научимся создавать бины без лишних усилий. Для этого из Editor > File and Code Templates нужно переместиться в Editor > Live Templates. Здесь можно описывать шаблоны, распознающиеся при наборе. В своей среде разработки я определил отдельный подвид для использования со Спрингом. В нём создаём шаблон:
@Bean public $CLASS_NAME$ $mthdName$() { return new $CLASS_NAME$($END$); }
Переменная CLASS_NAME
задаётся пользователем во всплывающем окне и помимо прямого назначения используется для создания имени метода:
Переменная mthdName
использует встроенный метод camelCase()
, которому передаётся значение CLASS_NAME
. Настройка происходит в Edit variables:
Переменная $END$
означает место постановки указателя после отрисовки. У нашего бина наверняка есть зависимости (внедряются через конструктор), поэтому их нужно сделать аргументами метода, возвращающего наш бин:
Теперь пройдёмся по приложению сверху донизу и посмотрим, какие ещё повседневные задачи можно ускорить таким нехитрым способом.
Сервис
Здравый смысл подсказывает, что данные способ будет наиболее полезен в случаях, в которых у нас есть большой объём кода, который кочует с места на место. Например, обычный спринговый сервис наверняка зависит от репозиториев (а значит нужна транзакционность), делает какое-никакое логирование, а зависимости внедряются через конструктор. Чтобы каждый раз не перечислять все аннотации над новосозданным классом опишем шаблон:
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end #parse("File Header.java") import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @Log4j2 @Service @Transactional @RequiredArgsConstructor public class ${NAME} { }
В действии:
Далее идёт опостылевшее перечисление зависимостей. Выше мы использовали встроенный метод camelCase()
для описания имени метода, возвращающего бин. Им же можно создавать имена полей:
private final $CLASS_NAME$ $fieldName$; $END$
Чтобы каждый раз не нажимать Ctrl+Alt+L (выравнивание) включайте галочку Reformat according to style и среда всё сделает за вас:
Репозитории и сущности
Даже в самых проcтых сущностях у нас есть множество импортов аннотаций. Для них можно создать очень действенный шаблон:
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end #parse("File Header.java") import lombok.Getter; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Getter @Table @Entity public class ${NAME} { @Id private Long id; }
В 2019 году если вы используете Хибернейт, то наверняка вы также используете Спринг Дату, а коль так, то нужно создавать репозитории. Давайте ускорим их создание:
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end #parse("File Header.java") import org.springframework.data.jpa.repository.JpaRepository; public interface ${Entity_name}Repository extends JpaRepository<${Entity_name}, ${Key}>{ }
Было бы здорово, если при установке курсора на сущность (класс помеченный @Entity
и @Table
) и нажатие Alt+Enter «Идея» предлагала бы сразу создать репозиторий, но она пока не настолько умна :). В настоящее время у пользователей нет возможности изменять/добавлять намерения (Editor > Intentions), но можно написать свой плагин:
Тестирование
Вообще, чем больше в вашем коде шаблонных конструкций, тем больший выигрыш даёт автоматизация. Одними из наиболее повторяемых кусков работы являются тесты. Кто смотрел доклад Кирилла Толкачёва «Проклятие Spring Test», те знают лёгкий способ заставить контекст подниматься только один раз для всех тестов: создание абстрактного класса и наследование всех тестов от него.
Описав что-то вроде
package com.example; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.transaction.annotation.Transactional; import org.springframework.test.context.junit.jupiter.SpringExtension; @Transactional @SpringBootTest @ExtendWith(SpringExtension.class) public abstract class BaseTest { }
мы можем легко заставить все новосозданные тесты наследовать BaseTest
. Для этого нужно изменить шаблон, создающий тест по умолчанию:
В коде:
import com.example.BaseTest; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; #parse("File Header.java") class ${NAME} extends BaseTest { ${BODY} }
Дальше мы описываем зависимости. Я не хочу каждый раз набирать
@Autowired private MyService service;
поэтому в разделе Editor > Live templates пишем
@Autowired private $CLASS_NAME$ $fieldName$; $END$
Переменная $fieldName$
описывается точно так же, как выше в примере создания бина за одим исключением: чтобы сразу после создания поля курсор не переключился на него, необходимо выставить галочку Skip if defined:
В основном @Autowired
нужен только для полей класса, так что не забудьте выставить Declaration в выпадайке Applicable in:
Смотрим:
Кстати, раз уж мы создаём тест для определённого класса, то ничто не мешает нам внедрить нужную зависимость сразу при его создании (toCamelCase()
здесь не работает, так что используется Велосити):
import com.example.demo.BaseTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; #parse("File Header.java") class ${NAME} extends BaseTest { #set($bodyLength = $NAME.length() - 4) #set($field = $NAME.substring(0, 1).toLowerCase() + $NAME.substring(1, $bodyLength)) @Autowired private ${CLASS_NAME} ${field}; ${BODY} }
Мой опыт подсказывает, что по возможности все тесты должны быть сквозными. Даже если проверяется сервис, достающий сущность и отрезающий часть одного из её полей, то всё равно лучше получить сущность честно, т.е. из БД. Поэтому для большинства тестов я беру честные данные одного из окружений и загружаю их перед запуском теста с помощью @Sql
.
Выборку данных нужно делать руками под каждую задачу, а вот связывание их с нужным тестом можно легко автоматизировать. Снова заходим в Editor > Live templates, раздел JUnit и пишем:
@Sql("/sql/$CLASS_NAME$.sql") $END$
Теперь набирая sql
у нас появляется выпадайка с 1 записью, выбрав которую получаем:
@Sql("/sql/MyEntityRepositoryTest.sql") class MyEntityRepositoryTest extends BaseTest { }
Обратите внимание, что файл, на который мы ссылаемся, ещё не существует, поэтому при запуске теста в сыром виде он обязательно упадёт. Однако, начиная с версии 193.1617 «Идея» подсвечивает несуществующий файл, и что самое важное, — предлагает создать его!
Постфиксы
Один из мощнейших инструментов — создание/дополнение кода с помощью постфиксных выражений (завершений). Простейший пример:
Завершений существует огромное множество, посмотреть их все можно в разделе Editor > General > Postfix Completion:
Здесь тоже открывается простор для разного рода экспериментов. Для себя я сделал завершение для подстановки переменной в проверочное выражение на основе AssertJ:
В жизни это выглядит так:
Полезные ссылки
Если вы собираетесь улучшить свои навыки работы с «Идеей», то обязательно посмотрите два замечательных доклада:
Антон Архипов — эффективная работа с IntelliJ IDEA
Тагир Валеев — Атомарный рефакторинг в IntelliJ IDEA: прогибаем IDE под себя
На этом всё, реже отвлекайтесь и помните, что время — наш единственный действительно невозобновляемый ресурс.
ссылка на оригинал статьи https://habr.com/ru/post/465433/
Добавить комментарий