Date Tags kotlin

Kotlin

Данный материал я написал примерно год назад. Была идея переработать его, но сейчас я решил его опубликовать в изначальном виде.

Первое впечатление

После перехода в новую компанию оперативно изучил 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