Привет!
Хотелось бы сегодня рассказать про мой процесс перехода с Groovy на Kotlin, об отличиях, преимуществах и недостатках этих языков. Я подразумеваю что у вас уже есть опыт работы с Groovy, вы знаете и используете большинство вспомогательных методов/приемов (далее сахар).
Начнем с предпосылок смены языка на нашем проекте. Нам потребовалось срочно подготовить проект под увеличивающиеся нагрузки (примерно в 10-100 раз). Что, во-первых, привело к жесткой смене архитектуры, а во-вторых, мы решили сменить и основной язык программирования.
Как многим, наверное, известно, в большинстве случаев Groovy работает медленнее Java. Единственный способ сделать Groovy сравнимым по скорости с Java — это @CompileStatic. Но, если вы работали с Groovy и у вас накопилось достаточно много Groovy кода, где вы не гнушались использовать все прелести динамической типизации, как в примере ниже, то у вас не получится просто взять и расставить везде @CompileStatic, придется переписывать много кода.
def map = [:] map["a"] = 5 def a = map.a
И, практически, это превращает Groovy-код в статически типизированный. И писать на нем становится не так приятно.
В то же время, Kotlin статически типизированный из коробки. Вам не нужно ничего дополнительно писать, чтобы Kotlin код компилировался и работал так же быстро, как и Java-код. То есть, простыми словами, если написать компилируемый, рабочий код на Java, Kotlin и Groovy, и запустить его, не парясь с оптимизациями, то мы увидим, что скорость Java и Kotlin будет на одном уровне, а Groovy проиграет в несколько раз.
Это была одна из основных причин перехода на Kotlin. Кроме того, мы столкнулись с тем, что в некоторых местах нашего приложения у нас слишком много динамической типизации и становится практически невозможно работать с такими данными. К примеру, у нас есть Map, в которой содержатся объекты разных классов, в некоторых из них тоже лежат Map со всякими разными данными, и все это может генерироваться в 3 разных местах. И чтобы понять, что на самом деле содержится в объекте, и какие у него вообще могут быть состояния уходит очень много времени.
Самое смешное, что изначально все классы были красивыми, чистыми (иначе их бы не пропустили на code-review), но, со временем, мы добавляем 1 поле, потом еще 1, потом вроде бы уже стоит сделать рефакторинг, но времени нет и постепенно простая структура обрастает кучей лишних полей, данных и возможных состояний. И бороться с этим в динамически типизированном языке очень сложно.
И последняя причина перехода на Kotlin — он просто модный и молодежный 🙂
Итак, я уже больше года пишу разрабатываю на Kotlin. До этого 3 года писал на Groovy. Чистую Java практически забыл 🙂 Рассмотрим преимущества и недостатки Kotlin в сравнении с Groovy, с точки зрения разработчика.
Преимущества Kotlin
- Статическая типизация. В любой момент времени мы точно знаем тип каждой переменной. Если это не так — компилятор упадет с ошибкой.
- Скорость компиляции и выполнения, сравнимая с Java.
- Еще более строгая типизация, в сравнении с Java. Nullable/notnullableтипы, mutable/immutableколлекции, мапы и т.д. Это позволяет еще больше контролировать свой код. Ты можешь просто взглянуть на тип (или спросить IDE) и ты будешь знать возможен ли там null, может ли кто-нибудь добавить что-нибудь в коллекцию и т.д. Для этого больше не нужно искать все использования.
- Удобнее пользоваться функциями (они же лямбды, они же closure), т.к. нам точно известно, что функция ожидает на вход и что она вернет
- Присутствует большая часть сахара из Groovy
- Классы, поумолчанию, final.
- Встроенные Singleton объекты (object BlahBlah{ … })
Недостатки Kotlin
- Нельзя быстренько налепить костылей. Например, нам нужно вернуть 3 разных объекта, в Groovy мы можем вернуть Map с тремя этими объектами и забыть. В Kotlin придется или сразу писать чистый код, или создавать класс.
- Бывает сложно написать шаблонный класс. Из-за строгой типизации нужно очень внимательно сделать за параметрами шаблонного класса, особенно когда случай не тривиальный.
- Новичкам сложно начинать писать на Kotlin, т.к. он требует писать код однозначно и аккуратно. Особенно новичкам сложно с nullable/not-nullable типами.
- Странным образом реализован аналог статических методов/полей. По сути, в Kotlin нет статических методов/полей, можно создать объект-компаньон-синглтон, в которые необходимо перенести все то, что было бы статическим в Java или Groovy
- Из-за того, что все классы по умолчанию final — есть проблемы с тестированием. Чтобы мокать такие классы необходимо либо помечать их как open, либо использовать новые библиотеки для тестирования, которые умеют мокать final классы.
- Интеграция Kotlin кода в Java/Groovy — не очень удобная. Не работают параметры по умолчанию, методы, которые находятся в объекте-компаньоне необходимо помечать аннотацией @JvmStatic. Расширения классов запускать тоже не очень удобно (пример ниже)
//Допустим у нас есть такое расширение для String fun String?.isValidPhoneNumber() = this != null && this.isNotEmpty() && ... //В Kotlin коде его можно использовать так: val string = "+79998887766" string.isValidPhoneNumber() //Но в Java/Groovy придется использовать следующую конструкцию def string = "+79998887766" FilenameKt.isValidPhoneNumber(string)
P.S.:
Пока писал этот пост, вспомнил, что часто сталкиваюсь с ситуацией, когда мне заранее точно известно, что коллекция/мапа обязательно должны быть не пустыми. Сейчас в Kotlin доступны вспомогательные классы/типизация для mutable/immutable nullable/not nullable коллекций. Я думаю, не пустая коллекция тоже бы пригодилась