Disclaimer: Я не имею никакого отношения к JetBrains, не получаю денег от продвижения Kotlin или от написания данной статьи. Весь материал — это лишь отражение моего личного опыта.
Выбор того или иного языка (и вообще любого инструмента) как правило продиктован как объективными, так и субъективными факторами. В каждом проекте могут быть свои особенности, влияющие на выбор. Я далек от того, чтоб устраивать очередной холивар на тему какой язык круче — языков много и у каждого своя ниша. Однако, на мой взгляд выбор между Kotlin и Java более определенный, поскольку Kotlin покрывает все возможности Java и добавляет много дополнительного функционала и сахара.
Исторически сложилось, что Java занял огромную часть рынка корпоративной разработки. Команды, начинающие новые проекты как в стартапах, так и в энтерпрайзе, по инерции в большинстве случаев выбирают в качестве основного языка именно Java. На мой взгляд, на сегодняшний день это является ошибкой, которая приводит лишь к увеличению стоимости разработки и дальнейшей поддержки кода.
В этой публикации я собираюсь объяснить свою позицию, а заодно опишу некоторые особенности перехода с Java на Kotlin как в условиях стартапа, так и в условиях крупного энтерпрайз-проекта.
Kotlin против Java
Можно было бы привести список из десятков-сотен преимуществ языковых конструкций в Kotlin против таких же конструкций в Java, но основной выигрыш от применения языка находится гораздо глубже.
На мой взгляд, Java зародилась как мощный ответ прежде всего C++. Поэтому в ней чувствуется сильное его влияние со всеми его недостатками. Проблема в том, что за 25 лет существования Java, она продемонстрировала значительную консервативность по сравнению с другими языками. В то время как множество языков экспериментировали с ключевыми словами типа async
, с пунктуацией языка (точки с запятой, скобки и пр.), Oracle хранила исключительную верность "традиционным принципам", что привело лишь к моральному устареванию самого языка.
Печально то, что разрыв лишь нарастает, несмотря на попытки его сократить со стороны Oracle. Например, дата-классы в Kotlin появились в самых ранних версиях, а некий их аналог в Java появился лишь в 14 версии, полагаю, как раз под давлением конкуренции с JetBrains.
Kotlin, напротив, стремился вобрать в себя все самые передовые практики от всех других языков разработки, при этом поддерживая интероперабельность с Java. Поэтому, сравнивая Java и Kotlin, мне приходит на ум аналогия со старым добрым и надежным паровозом с его чугунными поршнями (Java) и автомобилем с его персональной аудиосистемой, противоугонкой, хромированными ручками дверей и прочими "наворотами" (Kotlin).
Если вам в вашем проекте требуется "возить лес в Сибири" вдали от селений и с полным отсутствием бензина, то, вероятно, паровоз вам действительно подойдет лучше. Но большинство проектов все же находятся не в столь экзотических условиях. Поэтому тот факт, что на Kotlin проект получается раза в 2-3 компактнее, более легкочитаемый, более надежный в плане защиты от NullPointerException
, должен стать серьезным аргументом. Именно эти особенности и позволяют Kotlin снижать в 2 раза время разработки и количество багов, а в итоге, стоимость создания и владения программным продуктом.
Я лишь вкратце перечислю те преимущества, которые Kotlin предлагает из коробки, но которые с трудом достигаются в традиционном Java.
Множество элементов, сокращающих бойлерплейт: data class, getters&setters — в разы сокращает количество кода.
Более продуманная и юзабельная структура классов: вместо HashMap, например, Map (иммутабельный) и MutableMap (мутабельный) — позволяет с легкостью входить в язык новичкам и концентрироваться на бизнесовой постановке, а не на способах реализации.
Прямо из коробки идут различные защиты, например от
null
:
var str: String = "" str = null // Здесь будет ошибка компиляции, а не runtime var nullableStr: String? = null // а так можно
— что приводит к значительным сокращениям ошибок в программах и снижению стоимости поддержки.
На уровне языка идет поддержка асинхронных операций, которые существуют в Python и JS ES6 — приводит к упрощению работы с асинхронными и многопоточными операциями по сравнению с Java.
Kotlin Multiplatform — возможность компиляции программ на Kotlin одновременно для JVM, JS, Native (C/C++, Object C) — допускает снижение дублирования кода, более легкую интеграцию с фронтендами и более оптимальную разработку в архитектуре Serverless.
Domain Specific Language (DSL) — дает компактную и легкочитаемую замену билдерам.
Проблемы перехода на Kotlin
При обсуждении проекта с командами, возникают одни и те же вопросы по внедрению Kotlin. Давайте пройдемся по ним.
Где взять Kotlin разработчиков?
Kotlin хоть и относительно новый язык, но он уже завоевал значительную нишу. Специалистов, знакомых с Kotlin достаточно много на рынке. Сильно способствует этому то, что для Android Kotlin стал основным языком разработки.
В любом случае, любой разработчик, знакомый с Java, вполне успешно и быстро переходит на Kotlin (см. следующий вопрос).
Сколько нужно времени для подготовки разработчиков?
Kotlin разрабатывался на базе Java и совместим с ним на 100%. Поэтому для перехода на него не обязательно проходить какие-то специализированные курсы. Начать разрабатывать на Kotlin можно с первого дня. Базовых отличий не так много между языками. Например, if
работает и там, и там практически одинаково, дополнительно в Kotlin его можно использовать в виде выражения:
val x: Int = if (a == 1) 2 else 3
Существенные языковые несовместимости между языками, естественно, присутствуют, но они довольно быстро осваиваются и продуктивнее их прочитать на официальном сайте Kotlin, чем слушать курс лекций.
Однако, надо понимать, что новичок будет пользоваться Котлином в стиле Java, т.е. без использования всех преимуществ языка. Для освоения практики использования Kotlin с его особенностями, безусловно требуется практический опыт. Значительно ускорить вход в язык помогает опытный разработчик, который может отревьюировать код и подсказать лучшие решения, либо практические курсы.
В любом случае, некоторая заминка в связи с переходом на новый язык, полностью покрывается значительным ускорением разработки на Kotlin.
Как быть с легаси кодом?
Kotlin полностью интероперабелен с Java, что позволяет прямо в программе на Kotlin использовать код, написанный в Java. Также в саму Intellij Idea включен плагин, который на лету умеет конвертировать куски кода (либо весь файл) из Java в Kotlin. Надо понимать, что качество автоконвертации не сравнится с работой человека, но тем не менее, инструмент очень полезный.
Также Kotlin позволяет писать код одновременно на Java и Kotlin в одном модуле. Для этого можно настроить сборщик либо на расширения, либо на директории, разнеся их в src/main/java
и src/main/kotlin
. Тут опять нужно знать о нюансах: код из Java легко подхватывается в коде на Kotlin, но для обратной совместимости приходится прилагать определенные усилия. Например, по умолчанию в Kotlin вместо статических методов используются объекты-компаньоны и для экспорта в Java требуется применить аннотацию @JvmStatic
.
Начинающие разработчики на Kotlin бывает совершают ошибку, дублируя библиотеку для Java и для Kotlin. Поддерживать две версии библиотеки будет неэффективно, поэтому рекомендую либо просто продолжать ее разработку на Java и просто импортировать в Kotlin, а еще лучше сразу конвертировать ее в Kotlin и обеспечить совместимость с легаси семантикой.
У нас стартап, зачем нам Kotlin?
Довольно часто для стартапов применяется даже не Java, а такие языки как PHP, Python, JavaScript (NodeJS). В каких-то случаях это может быть оправдано, но надо иметь в виду следующее. Перечисленные языки могут себя неплохо показать для прототипирования, но из-за отсутствия строгой типизации, они меньше подходят для долгосрочных, крупных проектов. Просто потому, что строгая типизация позволяет избегать множества ошибок на этапе даже не компиляции, а на этапе написания кода в IDE. Ошибки, допущенные из-за отсутствия проверки типов, выливаются в простои в боевом режиме, приводя к убыткам. Поэтому вам нужно тщательно взвесить все выгоды от быстрой разработки на языках без строгой типизации и убытки от их применения в продуктовом режиме.
Довольно часто бытует мнение, что первую продажу можно сделать на каком-нибудь PHP, а затем мигрировать на Java/Kotlin. Мой опыт говорит, что сделать первую продажу можно вообще без кода хоть на PHP, хоть на Kotlin. Но времени для перехода с одного языка на другой на практике никто не даст при развитии стартапа. Каждый день будут задачи, которые нужно сделать вчера.
Безусловно, на любом языке можно написать плохо. Но PHP, Python, JS дают гораздо больше возможностей для низкокачественного кода, чем Java/Scala/Kotlin. И чем хуже изначально написанный код, тем хуже он поддерживается дальше, приводя к еще большим костылям. Я сталкивался со стартапом, который стартовал нормально, но за 7 лет своей работы получил совершенно нечитаемый и неподдерживаемый код на Python. Ситуация оказалась патовой: его нельзя было просто так закрыть из-за наличия обязательств, и нельзя было продолжить развивать из-за необходимости полностью переписать проект, на что бюджета уже не было.
Поэтому моя рекомендация для долгосрочных и потенциально крупных проектов — использовать Kotlin. Ускорение выхода на рынок можно реализовать не снижением качества разработки, а другими подходами типа правильного выбора MVP, гибкой архитектурой и сокращением расходов на техподдержку при качественно написанном коде.
Особенности разработки на Kotlin
Как я уже отмечал выше, применение Kotlin в реальных проектах может существенно отличаться от применения Java, благодаря тем дополнительным средствам, которые он предоставляет. Особенности эти, в итоге влияют в том числе и на общую архитектуру проекта. Поскольку особенностей много, я лишь для примера приведу несколько.
Ненулевые переменные и свойства класса
Kotlin предоставляет мощнейший элемент защиты от null
. Приводит это к тому, что работать с nullable
переменными просто неудобно — их ведь нужно всегда проверять на null
. Хоть такая проверка выполняется очень просто в Kotlin, хочется избегать ее чрезмерного использования. Достигается это тем, что при создании большинства классов я определяю пустой элемент:
data class SomeClass(
var x: String = "",
var y: Int = -1
) {
companion object {
val NONE = SomeClass()
}
}
, который и используется при инициализации в качестве значения по умолчанию вместо null
.
Существует множество случаев, когда нам просто необходимо делать свойства нулевыми. Например, в API или в хранилище мы не можем быть уверенными, что нам придет всегда non-nullable
значение. Как раз здесь и играет свою роль DDD (domain driven design). Мы для отправки данных или для сохранения делаем отдельные DTO-классы, которые содержат в себе нулевые поля, а в рабочие модели мы их мапим со всеми проверками:
data class StorableObject(
var id: String? = null,
var data: String? = null
) {
// Место размещения мапера зависит от задачи
constructor(wm: WorkModel): this(
id = wm.id.takeIf { it != -1 }?.toString(),
data = wm.data.takeIf { it.isNotBlank() }
)
// Место размещения мапера зависит от задачи
fun toWorkModel(): WorkModel = WorkModel(
id = id?.toInt() ?: -1,
data = data ?: ""
)
}
Таким образом, простое использование Null-safety в Kotlin стимулирует нас применять практику DDD, что в свою очередь делает наш код более гибким, защищенным от изменений API или внутренней логики.
Kotlin Multiplatform (KMP)
KMP — это довольно мощное средство и его стоит использовать в проектах при интеграции с JS и Native компонентами системы. На практике, KMP вполне неплохо себя показывает при описании API для потребления Single Page Application (SPI) типа React, Angular, Vue, а также мобильными приложениями под Android и iOS.
Но лично я предпочитаю разрабатывать фронтенд на Dart/Flutter, а с этим фреймворком простой интеграции через KMP пока не существует. Интеграцию между бэкендом на Kotlin и фронтендом на Dart можно реализовать с использованием OpenAPI, AsyncAPI, gRPC и других кодогенераторов.
На бэкенде KMP может быть применен, например, при использовании Serverless архитектуры, в том числе Google Cloud Functions, Yandex Cloud Functions, AWS Lambda и др. аналогов. Особенность бессерверных архитектур в том, что в них важна скорость старта приложения. Поэтому код на Python или NodeJs может показывать гораздо более низкое время отклика, чем на JVM. Использование KMP позволяет компилировать код в JavaScript или в Native executable, что вполне решает проблему быстрого старта.
Недостатки Kotlin
Наверное есть какие-то недостатки более бытового характера. Но для меня самым существенным недостатком является отсутствие фронтенд-фреймворка для Kotlin. Ktor, при всех его преимуществах, не достиг уровня фронтенд-фреймворка. Я как-то видел набор в Jetbrains фронт-разработчиков и думаю, что вскорости такой фреймворк будет анонсирован. Однако, на сегодняшний день на фронтенде я предпочитаю использовать Google Flutter, который использует язык Dart. Возможно для кого-то окажется более приемлемым переход на Dart как на фронтенде, так и на бэкенде вместо Котлина.
Заключение
В заключении хочу кратко суммировать основные утверждения настоящей статьи.
- Kotlin — это действительно мощный инструмент, который превосходит классическую Java по многим параметрам. Поэтому я ожидаю, что в ближайшие 5 -10 лет произойдет существенное вытеснение им последней с рынка.
- Перевод реальных проектов на Kotlin не создает непреодолимых препятствий, наоборот, его уместно осуществлять на легаси-проектах, так и новых; как в корпорациях, так и в стартапах.
- При том, что переход на Kotlin довольно прост, недостаточный опыт чреват Java-стилем, что неэффективно. Для более полноценного использования этого инструмента, рекомендую получение опыта, привлечение экспертов, либо прохождение практических курсов разработчиками.
- Использование Kotlin может заметно влиять на архитектуру проекта, делать ее более стройной, а также позволяет применять техники, которые неприменимы в Java, в частности Kotlin Multiplatform.
- Пока не существует достаточно мощного фронтенд-фреймворка на Kotlin. Кто-то может предпочесть Котлину Dart, который можно использовать одновременно на всех платформах как на фронтенде, так и на бэкенде.
androidovshchik
По-моему, он позиционируется больше как мультиплатформенный фреймворк для клиент-серверных приложений, и если есть что-то из фронтенда, то это только голый html, css
Фронтенд фреймворка действительно нет такого, чтобы был сравним с react, angular, vue, хотя для первого есть офф. обертка. Имхо разработчикам языка надо больше времени уделять javascript, это может невероятно сильно популяризировать язык. Пока ощущения двояки, вроде работает, вроде есть минимизация stdlib, вроде есть транслятор типов из typescript, но когда подключаешь сторонние библиотеки на javascript, чувствуешь боль от того, что для каждой (99% из 100) нужно
писатьгенерировать типы и весь этот boilerplate код таскать в одной папке с исходниками (который, кстати, устаревает быстрее, чем комментарии). По-сути это работа авторов библиотек, но они пока не заинтересованы в написании типов на kotlin, жалкоsvok Автор
Ktor состоит из серверной части и клиентской. Клиентскую часть в очень сильном приближении можно рассматривать как попытку создания фронтендного фреймворка.
Там много еще недоделок, но дело движется…
hrensgory
Kotlin-React ничуть не хуже простого React, обёртки добываются из TypeScript-овских при помощи Dukat (да, увы, готовых нет и вряд ли появятся в ближайшем будущем).
В целом, на Котлине вполне успешно можно писать фронтенд, при этом используя общие с бэкендом классы модели и всякие там утилитные методы, типа валидаций и преобразований из варяг в греки.
Минус — жирный, даже при использовании DCE и сжатия, js рантайм, тут надо осторожно — сейчас это приемлемо для многих приложений, но не для всех.
ookami_kb
Вот мне интересно (без сарказма) посмотреть на реальный проект, где получается действительно переиспользовать модели на бэке и на фронте. Я несколько раз пробовал – но это в лучшем случае какие-нибудь DTO, в которых логики нет, так что в крайнем случае можно что-нибудь на кодогенерации замутить. В остальном же мне один язык и там, и там только мешает – все-таки подход сильно разный, проще, когда и языки разные используются.
hrensgory
Да, конечно, речь идёт о DTO, которые летают между бэком и фронтом/мобилами (мы используем Kotlin MPP).
Мешает или помогает один язык и там и там — это вопрос, не имеющий, кмк, однозначного ответа. Такой язык как Котлин — нам помогает.
svok Автор
По моему опыту, на KMP удобнее делать интерфейсную логику доступа к бэку для фронта. Удобно же когда один раз сделал ее и разные фронты пользуются одним кодом.
Менее очевидно и более спорно использовать его для генерации внутренних моделей, которые могут включать логику. Например, на транспортном DTO расчет ФИО из Ф, И, О будет избыточным, но для внутренней модели это вполне нормально.
Ну и еще одно применение. Фронт бывает не только SPA. SSR часто делают на ноде, но ничто не мешает делать его на Kotlin — какая разница на чем шаблоны заполнять? В этом случае логику действительно можно будет шарить между тем же Реактом и SSR-ным бэкендом.
А вообще, новые возможности могут значительно повлиять на применяемые практики программирования. Возможно, наш предыдущий опыт мешает увидеть какие-то новые подходы, которые могут стать преобладающими на рынке.