Одно время, чтобы не отставать, так сказать, от прогресса, я ходил на StackOverflow code review, и переписывал код с использованием Stream API (и соответственно лямбда-функций). Иногда получалось хорошо и коротко и красиво, иногда не очень. Но это надо было сделать, - примерно как решать однотипные математические задачки в школе. И вот, вроде бы привык к “хорошему”, местами код даже начинал напоминать какой-то JavaScript. Но одно дело, делать что-то с нуля или решать задачки, а другое - традиционный такой проект, махровая Java.
Я с frontend-программистом как раз немножко общаюсь, прошу его быть богобоязненным и напоминаю о том, что я знаю, где он живёт. А всё дело в том, что он мне хочет присылать любое количество полей профиля, а мне надо с этим как-то жить. Итак, покажу, как это примерно выглядит:
@PostMapping("/profile/{profileId}")
public ResponseEntity<Profile> put(@PathVariable("profileId") Long profileId, @RequestBody ProfileDTO body) throws ApiErrorException {
final Profile profile = profileRepository.findOne(profileId);
if (body.getAddress() != null) {
profile.setAddress(body.getAddress());
}
if (body.getAvatar() != null) {
profile.setAvatar(body.getAvatar());
}
return ResponseEntity.ok(profileRepository.save(profile));
}
А всего там полей около 30, в четырёх разных классах. Сначала я чуть было не начал писать свой mapstruct, но осознав свою никчёмность, плюнул и начал копипастить. Копипастить оказалось не очень-то хорошо, неминуемо однажды я ошибусь. Тогда я задумался, как бы свести шанс ошибок к минимуму. Первым шагом на пути стало использование лямбда-функций и новых API Java. Меньше кода - меньше ошибок, тут надо сделать лишь две замены вместо трёх:
@PostMapping("/profile/{profileId}")
public ResponseEntity<Profile> put(@PathVariable("profileId") Long profileId, @RequestBody ProfileDTO body) throws ApiErrorException {
final Profile profile = profileRepository.findOne(profileId);
Optional.ofNullable(body.getAddress()).ifPresent(profile::setAddress);
Optional.ofNullable(body.getAvatar()).ifPresent(profile::setAvatar);
return ResponseEntity.ok(profileRepository.save(profile));
}
Следующим этапом стал Live Template. Дело в том, что я всегда и принципиально пользуюсь JetBrains IDEA, и там есть возможность задать собственные шаблоны для генерации код. В данном случае я сделал шаблон sto
(хотел переименовать body
в source
, а profile
в target
для универсальности, а шаблон начать с st
). Оказалось, что от переименования body
можно отказаться, а вот profile
таки пришлось переименовать в target
. И после этого родилась первая версия живого шаблона:
Optional.ofNullable($SELECTION$).ifPresent(target::set);
Например, я писал body.getBio()
и нажимал Ctrl+Alt+T для вызова живого шаблона. В результате я получал
Optional.ofNullable(body.getBio()).ifPresent(target::set);
И курсор был на слое Optional
. Таким образом, перейдя к слову set
, можно было вызвать автодополнение и выбрать правильный метод. Честно говоря, ненамного лучше Ctrl+C/Ctrl+V, не правда ли? Тогда я полез разбираться в переменные, и нашёл там возможность выполнить динамический скрипт на Groovy. Шаблон стал таким:
Optional.ofNullable($SELECTION$).ifPresent($MNAME$);
А переменной $MNAME$
я присвоил значение
groovyScript('_1.trim().replaceAll("(\\w+)\\.get(\\w+)\\(\\)", "target::set\$2")', SELECTION)
И что? Правильно, ничего. Почему? А чёрт его знает, спрошу у разработчиков. Тогда родилась третья, стрёмненькая версия, но она существенно лучше первой. Если мне не дают подставить свой текст, подставлю часть текста и вызову автокомплит. Сказано - сделано, шаблон стал таким:
Optional.ofNullable($SELECTION$).ifPresent(target::set$MNAME$);
А значение переменной $MNAME$
стало таким:
completeSmart()
Теперь я пишу body.getBio()
, нажимаю Ctrl+Alt+T, вызываю живой шаблон sto
и получаю на выходе:
Optional.ofNullable(body.getBio()).ifPresent(target::set);
При этом курсор стоит после слова set
, и вызвано автодополнение. К сожалению, автодополнение совсем не умное, потому что предлагает вызвать любой сеттер, вне зависимости от типа. Если кто не знал, в IDEA есть более умное автодополнение: например, я пишу строчку target.setBio(body.
и нажимаю Ctrl+Shift+Пробел. Код сам превратится в target.setBio(body.getBio());
, потому что больше ни один из методов класса ProfileDTO
не возвращает объект класса Bio
. Это, кстати, можно попробовать сгрузить в виде Feature Request.
Резюмирую: используя возможности IDEA, удалось написать живой шаблон, который весьма компактен за счёт использования Java 8, который безопаснее чем копипаста, и экономит время.