С тех пор, как Google объявила об официальной поддержке Kotlin в Android, всё больше разработчиков хотят использовать его в своих новых и существующих проектах. Поскольку я также большой поклонник Kotlin, я не мог дождаться, когда смогу использовать Kotlin в своём рабочем проекте. В конце концов, Kotlin полностью совместим с Java, и все разработчики просто в восторге от этого. Так что же может пойти не так?
Ну, на самом деле многое может пойти не так. Меня просто пугает то, что на странице официальной документации Android «Начало работы с Kotlin» написано, что если вы хотите перенести существующее приложение на Kotlin, вы должны просто начать писать модульные тесты, а затем, после небольшого опыта работы с этим языком, вы должны писать новый код на Kotlin, а существующий Java-код просто конвертировать.
В этой статье я расскажу вам, что произошло бы, если бы я решил сразу же следовать этой стратегии на проде, а не попробовать её на своём маленьком проекте.
Познакомьтесь с моим маленьким проектом
У меня был проект, который я создал более года назад на Java, и мне нужно было немного его изменить и добавить один новый экран. Я подумал, что это отличная возможность проверить, действительно ли миграция существующего проекта на Kotlin очень проста, как все говорят. Исходя из рекомендаций официальной документации, я начал с написания на Kotlin модульных тестов. Новые классы я тоже писал на Kotlin и конвертировал некоторые существующие. Казалось, что всё замечательно, но через некоторое время я обнаружил одну неприятную проблему.
Kotlin + Lombok = :(
В моем приложении я использовал библиотеку Lombok для генерации геттеров и сеттеров. Lombok — это обработчик аннотаций для javac. К сожалению, компилятор kotlin использует javac без обработки аннотаций [источник]. Это означает, что методы, созданные при помощи Lombok, будут недоступны в Kotlin:
Но вы можете сказать: «Хорошо, это будет не очень круто, но в принципе можно вручную создать геттеры и сеттеры для тех полей, которые будут необходимы в коде Kotlin. В конце концов, не нужно этого делать во всём проекте сразу».
Я подумал так же. Но с реальной проблемой столкнулся тогда, когда захотел добавить новый экран в приложение.
Kotlin + Lombok + Dagger 2 = :(((
В моём приложении используется Dagger 2 для внедрения зависимостей. При создании нового экрана я обычно создаю структуру MVP: Activity, Presenter, Component, Module и Contract. Все зависимости для презентора внедряются с помощью Dagger. Activity вызывает DaggerSomeComponent.builder().(...).build().inject(this)
, чтобы внедрить презентор с необходимыми для себя зависимостями.
Использование Dagger 2 вместе с Kotlin — не проблема. Только перед этим нужно применить kapt-плагин, который создает необходимые самогенерируемые классы для Dagger.
И вот здесь всё начинает разваливаться
Без kapt-плагина я не мог использовать сгенерированные Dagger-классы в файлах Kotlin. Но после того, как я добавил этот плагин, все методы, созданные Lombok, исчезли!
До применения kapt-плагина:
После применения kapt-плагина:
И, к сожалению, решения этой проблемы нет. Вы можете применять либо только kapt-плагин, либо только Lombok. К счастью, поскольку это был всего лишь мой маленький проект, я просто удалил Lombok и сам написал геттеры и сеттеры. Но в этом проекте было всего около 50 сгенерированных методов. В проекте, который мы поддерживаем на работе, у нас их около тысячи. Удаление Lombok из этого приложения просто невозможно.
Также, очевидно, что отказ от kapt-плагина — не выход из ситуации. Без него вы не сможете использовать Kotlin в классах, где используется Dagger. В моем случае я должен был бы реализовать Activity, Component и Module на Java, и только Contract и Presenter могли быть написаны на Kotlin. А смешивание файлов Java и Kotlin — это определенно не здорово. Вместо плавного перехода с Java на Kotlin вы просто создали бы большой беспорядок.
Вот так выглядела бы эта ужасная полиглотная MVP-структура без kapt-плагина:
Но я всё же хочу перейти на Kotlin. Что мне делать?
Один из способов — использовать разные модули. Kotlin не увидит методы, которые Lombok будет генерировать в исходном коде, но будет видеть их в байт-коде.
Лично мне кажется, что это и есть самый предпочтительный путь. Если вы выводите Kotlin в отдельные зависимые модули для каждой фичи, вы уменьшаете риск таких проблем с совместимостью, с которыми столкнулся я, или более сложных, перечисленных в официальном гайде.
И это ещё не всё. В смешивании файлов Kotlin и Java без чёткого разделения есть много других недостатков. Это заставляет всех разработчиков, работающих с вами над проектом, знать оба языка. Также это уменьшает читаемость кода и может привести к увеличению времени сборки [источник].
Кратко
- методы, сгенерированные Lombok, не видны в Kotlin;
- использование kapt-плагина ломает Lombok;
- без kapt-плагина вы не cможете использовать самогенерируемые классы для Dagger в Kotlin, что означает, что вам всё равно придётся писать новый код на Java;
- способ решить эту проблему — вывести Kotlin в отдельные модули;
- смешивание файлов Kotlin и Java в больших проектах без чёткого разделения может привести к неожиданным проблемам совместимости.
Комментарии (24)
r_ii
06.09.2018 17:35А мне вот интересно зачем тривиальные геттеры/сеттеры?
Guitariz
06.09.2018 17:46безопасность обращения к переменным.
r_ii
06.09.2018 18:05И в чем же безопасность проявляется? Ну возьмем например класс Movie из второго рисунка. Чем тривиальный геттер безопаснее public final поля?
Bringoff
06.09.2018 18:10+1Как вариант, поля не final, потому что где-то внутри класса модифицируются (мы ведь не видим весь класс, верно?), но снаружи менять их по очевидным причинам нельзя.
svr_91
07.09.2018 08:27Вот интересно, что в C++ даже в Core Guidelines призывают не использовать геттеры/сеттеры, а использовать структуры (CoreGuidelines)
В результате, в классах есть ну максимум один геттер/сеттер, а все остальное — это нормальные методы с нормальными именами, выполняющие нетривиальную функциональность. Неужели в яве с этим такие проблемы?
AlexanderG
06.09.2018 17:51+1ИМХО, один из важнейших недостатков Java в сравнении с C# это отстутствие автопропертей. Весь этот геттеро-сеттерный бойлерплейт дико замусоривает классы и по размеру сравним иной раз с кодом, реализующим функциональность.
Bringoff
06.09.2018 18:14Мораль проста — не используйте Lombok. Я сейчас на большом проекте, где он используется, и за полгода им так и не проникся. В тривиальных случаях он помогает избавиться от нескольких строк бойлерплейта, но как-то это недостаточно весомая причина для +1 источника магии в коде.
DrAlan
06.09.2018 22:52Возможно в вашем проекте он не так уж и обходим.
Например у меня много микро-сервисов. В не которых из них могут быть поджо на десятки полей. Классы могут инжектиться до десятком (очень редко конечно) сервисов.
В первом случае писать толпу гетр сетеров во втором случае писать конструктор на несколько параметров и поддерживать его при изменениях кода.
А так же отличный фукционал генерации статик конструкторов и билдеров.
В целом на ломбок очень даже подходит на бэкенд задачах с толпой логики и энтетей.Maccimo
08.09.2018 00:27+1писать толпу гетр сетеров
Вы так говорите, словно это проблема. В современных IDE есть генераторы геттеров/сеттеров и даже в простом текстовом редакторе с этой задачей на ура справится банальный поиск с заменой. Два у нас поля или двадцать особой роли не играет.
gildor
06.09.2018 20:09В смешивании файлов Kotlin и Java без чёткого разделения есть много других недостатков.
Это каких? Могли бы привести конкретные примеры? Собственно для таких юзкейсов Котлин и создавался и отлично с ними работает.
Упомянутое замедление сборки может произойти конечно (из-за дополнительного анализа java кода компилятором Kotlin), но оно не критичное и на глаз его сложно заметить.
Это заставляет всех разработчиков, работающих с вами над проектом, знать оба языка.
Ну это не выглядит проблемой, без знания Java в Android разработке всяко никак, ну а обучить Java разработчика котлину на приемлемом уровне дело недели-двух
franzose
07.09.2018 00:56«pet project» — это не проект про домашних животных, это «детище, любимый проект». Хотя в контексте статьи ваш перевод этой фразы получился очень даже к месту))
yanex
07.09.2018 08:34А сможете выложить этот игрушечный проект к нам в ишью-трекер (https://kotl.in/issue)?
Мы бы поисследовали и, вероятно, исправили бы этот баг (впрочем, есть подозрение в неверной конфигурации проекта).Devcolibri Автор
07.09.2018 08:35Это перевод статьи, поэтому, к сожалению, нет такой возможности.
Напишите автору оригинальной статьи, думаю он с радостью это сделает.
anonymous
08.09.2018 18:08-1Ужас какой. Какие геттеры/сеттеры?
data class Movie( val id: Long, val title: String, val posterUrl: String )
Всё! У вашего entity теперь есть и get (чтобы были set нужно val поменять на var), а также equals(), hashcode(), toString() и clone(). Метод clone() — правильный подход. Должны быть лишь геттеры. Хочешь менять — создавай клон!
Перед тем, как перетаскивать проект с Java на Kotlin стоит Kotlin изучить.
semyong
Можно применить delombok и постепенно переписывать на kotlin.