Данный материал я написал примерно год назад. Была идея переработать его, но сейчас я решил его опубликовать в изначальном виде.
Первое впечатление
После перехода в новую компанию оперативно изучил Kotlin. Главная причина — 99% проектов, с которыми связан, используют этот язык. До смены работы Kotlin видел только в докладах на конференциях и один раз писал мини-задачки в поезде по дороге с Jpoint 2018. Kotlin освоить легко. Java опыт и увлечение функциональными языками помогают в этом. Я почти влюбился в этот язык. Из-за "влюбился" и "почти", родилась идея записать мысли о Kotlin в этой статье.
Kotlin чудовищно прагматичный язык. Похоже, каждая синтаксическая конструкция родилась из практических задач, которые приходится решать разработчикам каждый день. Оригинальный конструкций мало — даже затрудняюсь назвать хотя бы одну. У создателей не было задачи удивить мир новыми подходами. Kotlin — это компиляция практик, которые выжили и доказали успех в других языках. Напоминает подходы Apple — найти рабочие решения у конкурентов и соединить в одном идеальном продукте.
"Влюбился"
Nullability
Когда пишут о Kotlin, в 90% случаях касаются темы Nullability и безопасных программ без NPE. Null safe подходами Kotlin пропитан насквозь. При этом больше впечатляет система типов с Nullable, чем синтаксический сахар, который вырос вокруг не. Прощайте @Nullable
, @NotNull
и, вероятно, @NonNull
! А также решения, построенные на договоренностях, IDE и сторонних плагинах.
Перегрузка операторов и конвенции
Перегрузка операторов и конвенции позволяют существенно упростить код. Пример, из практики. Есть такая конструкция c DateTime
:
now.isEqual(date) || now.isAfter(date)
Ее можно, конечно, смело поменять на следующую конструкцию, в попытках что-то сэкономить:
now.compareTo(date) >= 0
Но Kotlin дает простой и понятный всем (всем ведь?) вариант:
now >= date
Посмотрите внимательнее на конвенции, связанные с операторами. Они сократят и улучшат ваш код.
DSL
Двинемся дальше и чуть-чуть поговорим про DSL. Продают эту тему очень активно. Иногда кажется, что процентов 80% в Kotlin было сделано для DSL. У меня за пару месяцев использования Kotlin появилось дикое желание написать свой DSL для описания BPMN. Язык буквально подталкивает тебя к тому, чтобы все заDSLить. Вот список крутых фич, которые делают Kotlin настолько благоприятным для написания своих DSL:
- Расширения
- Инфиксные функции
- Перегрузка операторов
- Конвенции — тут еще напомню про get метод
- Вынос lambda за скобки
- Lambda with a receiver
И еще немного
И еще чуть-чуть про любимые конструкции:
- Если все же вернутся к сахару, то
?.
и?:
выглядят прекрасно. Отличная компактная заменаOptional
. as?
— безопасность везде, в том числе во время приведения типов.- Активно продается идея immutability через пропаганду использования
val
. - Почти все является выражениями: и
if
, иwhen
, иtry
. - Data классы. Без комментариев.
- А еще замечаешь, что перестал ставить
;
в java на автомате.
"Почти"
Перейдем к поведениям, которые вызывают вопросы.
also
vs apply
Для начала хочется отметить also
vs apply
. Сама фича lambda with a receiver очень крутая, но иногда она позволяет изрядно запутать код. В практике столкнулся со следующим. Дано: много сгенерированных java объектов с конструктором без аргументов и толпой getter/setter'ов (в примерах указаны только поля для экономии пространства):
class User {
int id;
int systemId;
String name;
Account account;
}
class Account {
int id;
String name;
}
apply
позволяет вызывать конструктор с инициализацией нужных полей в достаточно компактном стиле:
val account = Account().apply {
id = 2
name = "Test account"
}
Выглядит красиво. Но мешанина начинается при инициализации вложенных объектов:
val user = User().apply {
id = 1
name = "Test user"
account = Account().apply {
id = 2
name = "Test account"
systemId = 3
}
}
Напутать в такой структуре очень легко. Держать в голове, где какой this
непросто. Есть вариант использовать labels, но код становится загроможденным. Можно заметить, что systemId
никакого отношения к Account не имеет, но его смело можно обновлять в ламбде, привязанной к account. Есть DslMarker
для ограничения скоупа, но он подходит в случае, если мы контролируем классы. Это доступно не всегда. Поэтому в таких случаях я считаю лучше использовать also
. Чуть больше кода, но и больше понимания, что происходит.
val user = User().also { user ->
user.id = 1
user.name = "Test user"
user.account = Account().also { account ->
account.id = 2
account.name = "Test account"
user.systemId = 3
}
}
Хочется, чтобы вложенные apply можно было запретить, но, к сожалению, не видел правила в линтере, которое это могло проверять во время сборки (например в detekt).
Companion object
Очень странный концепт. Существует, на мой взгляд, исключительно для того, чтобы протащить статические методы внутрь классов для полной совместимости с java. Больше похоже на подпорку и выглядит инородно. Есть вероятность, что я пока просто не полностью проникся этой идеей. Хотя есть один пример, в котором мне нравится использование компаньона:
companion object: KLogging()
Приоритет расширений
Немного вводит в замешательство то, что расширения имеют приоритет меньше чем функции в классе. Например:
class Foo {
fun bar() = println("member")
}
fun Foo.bar() = println("extension")
fun main() {
Foo().bar() // Выведет "member"
}
Возникают вопросы, так как по определению кажется, что расширения призваны наращивать функциональность. И мое первое ощущение — они должны быть приоритетнее членов класса, так как в любом случае сначала определяется класс, а потом уже расширение к нему. В общем, тут следует помнить, что это не так. И обращайте внимание на предупреждения от IDEA.
Kotlin MPP
Для меня очень спорная тема. Видимо, я наелся в свое время работой с GWT и у меня автоматом есть некое недоверие к подходу: давайте на всех платформах писать на одном языке. Раньше это была Java, теперь Kotlin. Но с той разницей, что надо собрать все компоненты своими руками. Не совсем понятно как происходит дебаг в браузере — снова плагин или просто source maps? Как вводить новых разработчиков в технологию — сразу учим js и kotlin, android и kotlin и т.д.? Без знания платформы все равно не обойтись.
Nullsafe и платформенные типы
Стоит помнить, что при вызове Java из Kotlin и Kotlin из Java ничто не гарантирует null-safe на стыке языков. Нету никаких магических проверок и гарантий для не null типов. Что использовать определяем сами. Иногда надо просто довериться java коду и местами использовать не-nullable типы. Отсюда совет — почитываем код библиотек, которые используем.
Материалы
Пару слов хочется уделить материалам, которые использовал во время знакомства с Kotlin.
- kotlinlang.org - вся информация по языку
- https://play.kotlinlang.org/byExample - неплохой tutorial для быстрого знакомства с языком
- Kotlin in Action - отличная книга закрывающая все базовые концепции Kotlin. Читал английское издание, воспринимается очень легко.
И в конце
Есть несколько тем, которые я пока обошел стороной. Очень хочется посмотреть, как работают корутины. Также в закладках лежит книга Effective Kotlin — надеюсь из нее почерпнуть еще полезные концепции.
В целом интересно наблюдать, как компания, делающая IDE, наращивает мощь и влияет на мир разработки. Раньше был только софт, помогающий лучше работать с кодом. Теперь новый jvm язык, который стремительно захватывает популярность.
У меня есть предположение, что Kotlin родился на основе статистики, которая доступна JetBrains через ее IDE. Это богатейший источник для дизайна и развития языка. Нет необходимости доверять мнению людей. Как известно все люди врут, но данные же так не могут. Мысль несколько утрированная, но все же хочется, чтобы Kotlin без остановки развивался и приносил еще больше полезных практических фич.
Comments
comments powered by Disqus