Главная » Хабрахабр » Идёт мобильный разработчик по лесу, видит — Котлин горит. Сел в Котлин и сгорел

Идёт мобильный разработчик по лесу, видит — Котлин горит. Сел в Котлин и сгорел

Говорят, все новые мобильные проекты на Андроиде пишут исключительно на Котлине. Мир сходит с ума. Вначале твои знания устаревают, ты вылетаешь с работы, живешь у теплотрассы, дерёшься с бомжами за еду и умираешь в безвестности, так и не выучив функционального программирования. В наше время очень опасно не учиться новым технологиям. Помогите Олегу-путешественнику найти смысл в Котлине! Поэтому я отправился на Курсеру изучать курс Kotlin for Java Developers и начал читать книжку (привет, abreslav, yole), поспрашивал друзей сами знаете откуда и вернулся назад с некой пустотой в душе.

  • Бонус: хаброопрос «Как вы используете Kotlin?»

a = b — запись в поле или локал, a[1] = 2 — запись в массив. ●  В Java ты чаще понимаешь по узкому контексту, что происходит. Без IDE ничего не поймёшь. В Котлине за любым простым выражением может стоять сколь угодно сложный код из-за всяких умностей вроде перегрузки. А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.

Стримы в джаве специально введены для различия между ленивой и неленивой коллекцией. ●  Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях, создавая кучу промежуточных неленивых копий. Да, есть инспекция в IDE для этого — потому что инспекции призваны исправлять недостатки языков.

Насколько хороша поддержка Kotlin в IntelliJ IDEA? ●  Кстати, об IDE. Есть большие сомнения. Она действительно лучше, чем для Java? Может быть, кому-то из JB хватит духу проадвокатировать по данному вопросу.

Что-нибудь типа seq.map .map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }. ●  Котлин форсит использование it, что приводит к нечитаемому коду. Имена переменным даны не зря! Что это вообще было? А тут получается монолог вроде «Возьмём это, прикрутим к нему то, потом его закрутим и если оно стало больше того, то наденем сверху шарнир».

И это считается идиоматично, Карл. ●  Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад и должны быть запрещены в декларации о правах человека. Читать такой код невозможно. В отличие от нормального if.

А тут всякие JvmStatic и JvmName, и код превращается в цирк с конями. ●  От интеропа с джавой кровь идёт из глаз.

Например, вот у нас есть такое:

class C { companion object { @JvmStatic fun foo() {} fun bar() {} }
}

Возможные варианты: Относительно помеченного аннотацией метода компилятор сгенерит и статический метод во внешнем по отношению к объекту классе, и метод экземпляра в самом объекте.

  • C.foo(); — работает
  • C.bar(); — синтаксическая ошибка, ибо метод не статический
  • C.Companion.foo(); — остается метод экземпляра
  • C.Companion.bar(); — единственный правильный способ

Окей, пошли дальше. Оправились от красоты решения? Теперь вы готовы понять и принять тот факт, что, например, нельзя одновременно объявить два таких метода:

fun List<String>.filterValid(): List<String>
fun List<Int>.filterValid(): List<Int>

Ведь их сигнатуры на уровне JVM совпадают: filterValid(Ljava/util/List;)Ljava/util/List;

Поэтому нужно подпихнуть специальный костыль:

fun List<String>.filterValid(): List<String> @JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>

А в Java-реальности они есть. А как вам такое: в Kotlin нет checked exceptions. Отряд специального назначения «Боевые протезы» имеет честь представить новый самоходный костыль @Throws:

@Throws(IOException::class)
fun foo() { throw IOException()
}

Но если вот это красиво, то что тогда ужасно? Можно долго рассуждать, что «джависты постоянно ноют, что тут всё не как в джаве».

В общем, рекомендуется открыть статью Java-to-Kotlin Interop и своими глазами посмотреть, как это выглядит.

Ведь регистр букв системно-зависим) — это страшно. ●  Автоматические геттеры/сеттеры с добавлением английского слова get и первой буквой проперти в большом регистре (видимо, в локали ENGLISH?

import java.util.Calendar
fun calendarDemo() { val calendar = Calendar.getInstance() if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // call getFirstDayOfWeek() calendar.firstDayOfWeek = Calendar.MONDAY // call setFirstDayOfWeek() } if (!calendar.isLenient) { // call isLenient() calendar.isLenient = true // call setLenient() }
}

●  Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.

Можно написать любой метод, слева поставить имя «принимающего класса», и всё — он расширен. Так как этой фичи нет в джаве, поясню. Давайте расширим MutableList функцией swap:

fun MutableList<Int>.swap(index1: Int, index2: Int) { val tmp = this[index1] // 'this' относится к листу this[index1] = this[index2] this[index2] = tmp
} val lst = mutableListOf(1, 2, 3)
lst.swap(0, 2) // 'this' внтури 'swap()' будет иметь значение 'lst'

Получается что-то вроде изнасилования с особым цинизмом. Работа экстеншн-методов возможна, даже если автор специально сделал финальный класс, явно показав, что не хочет сторонних расширений. Он должен думать обо всех экстеншн-методах, которые любые люди могут добавить в тот же класс? И конечно, они ломают совместимость: что будет, если в следующей версии библиотеки автор добавит методы с теми же именами, но с другим возвращаемым типом?

Хотя, казалось бы, вот эта фича могла бы произвести вау-эффект. Кроме того, невозможно сделать оптимизированные реализации экстеншн-методов в конкретных подклассах.

Например, reduce. ●  Библиотека местами не продумана.

Вот как выглядит reduce:

listOf(1, 2, 3).reduce { sum, element -> sum + element } == 6

Там есть только форма с identity (fold), но она не всегда применима.

listOf(1, 2, 3).fold(0) { sum, element -> sum + element } == 6

Аааа, уже неважно. Кстати, почему Хабр подсвечивает эти две строчки по-разному?

Соответственно, форма без identity кидает исключение для пустой коллекции. По сути, fold и reduce делают одно и то же, но fold требует определённого начального значения, а reduce использует в качестве этого начального значения первый элемент списка.

Почему не вернуть какое-нибудь Optional и дать пользователю самому решить, что делать в случае пустой коллекции? Всегда ли такое поведение всем нужно? Да или хоть null вернуть, раз уж это null-friendly язык.

Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? ●  Давайте ещё навалим про библиотеку. Это ж прямое поощрение плохого кода.

Напоминаю, дата-классы выглядят так:

data class User(val name: String, val age: Int)
val duncan = User("Duncan MacLeod", 426) val (name, age) = duncan
println("$name, $age years of age") // печатает "JaDuncan MacLeodne, 426 years of age"

Пара выглядит вот так:

val (name, age) = Pair("Java", 23)
println("$name, $age years of age") // тоже печатает "Java, 23 years of age"

А все потому, что внутри:

public data class Pair<out A, out B>( public val first: A, public val second: B
)

Складываем огуречные жопки с сотнями нефти, пишем фильтры-франкенштейны и в продакшен. Совершенно очевидно, что среднестатистический быдлокодер забьёт писать свои классы на второй день использования, и код превратится в кошмарную пародию на лисп. Быстро, просто, нечитаемо.

●  Очень странный момент — возможность не указывать возвращаемый тип метода (особенно публичного).

Программа падала в произвольном месте, а я не понимал — почему. Совсем недавно был случай в C++, от которого меня чуть не разорвало от злости. Это не синтаксическая ошибка согласно стандарту, а undefined behavior. Оказалось, в C++ можно не писать return в методе, который согласно сигнатуре должен что-то возвращать. Чудесный язык — в нем есть специальный синтаксис для неработающих методов. Соответсвенно, программа в рантайме падает с произвольной ошибкой. Эдакая параноидальная привычка. С тех пор я очень аккуратно проверяю, что мы обещали вернуть из метода и что отдали на выходе.

Это провоцирует людей писать нечленораздельную лапшу, в которой и ничего не понятно. И вот теперь, в лучшем в мире языке Kotlin мы можем вообще не указывать возвращаемый тип. Если метод a вызывает метод b, а тот метод c, а тот содержит в теле выражение when, в котором в ветках ещё три метода вызываются d, e и f, попробуй пойми тип метода а!

fun a(check: Int) = b(check)
fun b(check: Int) = c(check) fun c(check: Int) = when (check) { 1 -> d() 2 -> e() else -> f() } fun d() = "result 1";
fun e() = "result 2";
fun f() = "result 3"; fun main(args: Array<String>) { println(::a.returnType) for (i in 1..3) println(a(i).javaClass.name)

Меняешь возвращаемый тип метода f, и у тебя автоматом меняется возвращаемый тип метода а совсем в другом пакете, и ты не понимаешь, что происходит. Причём вначале вроде всё было просто и понятно, а в процессе эволюции поменялось, и капец.

Изначально в нашем примере выхлоп выглядел вот так:

kotlin.String
java.lang.String
java.lang.String
java.lang.String

Но стоит только поменять определения функций на вот такие:

fun d() = "1";
fun e() = 100500;
fun f() = listOf<String>();

И результат тут же изменится на

kotlin.Any
java.lang.String
java.lang.Integer
kotlin.collections.EmptyList

Для публичных методов явная спецификация API должна быть священной коровой, а Kotlin её не требует. Никакой кристаллизации API.

Из этой статьи может показаться, что все в Котлине плохо, но это очевидно, не так. Пожалуй, для начала достаточно. Как минимум, зарплата Kotlin-разработчика обычно неплоха 🙂

В недавнем докладе на Joker 2018 (есть слайды), Паша (asm0dey) Финкельштейн отмечал, что на бэкенде Kotlin помогает писать более красивый и лаконичный код (но не всегда это получается), на нем получаются более выразительные тесты, с ним работает GraalVM, и всё это с примерами для Spring, Spring Security, Spring Transactions, jOOQ, и т.п.

Неясно. Стоит ли переходить на Kotlin с Java для мобильных приложений? Давайте в нём покопаемся! В любом случае, Kotlin интересный.

Уже на этой неделе, 8-9 декабря 2018, пройдет конференция Mobius. Минутка рекламы. Кроме того, можно будет пересечься со множеством людей, реально использующих Kotlin, и узнать, зачем и как они это делают. На ней Святослав Щербина из JetBrains расскажет о том, как писать мобильные приложения на Kotlin Muplitplatform. Билеты можно приобрести на официальном сайте. Места все еще есть, а вот времени уже почти не осталось, так что, если хотите прийти, сейчас у вас последний шанс.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Перевод] Учебный курс по React, часть 1: обзор курса, причины популярности React, ReactDOM и JSX

Представляем вашему вниманию первые 5 занятий учебного курса по React для начинающих. Оригинал курса на английском, состоящий из 48 уроков, опубликован на платформе Scrimba.com. Возможности этой платформы позволяют, слушая ведущего, иногда ставить воспроизведение на паузу и самостоятельно, в том же ...

[Перевод] Разбираем лямбда-выражения в Java

Мы открыли его для себя совсем недавно, но уже по достоинству оценили его возможности. От переводчика: LambdaMetafactory, пожалуй, один из самых недооценённых механизмов Java 8. 0 фреймворка CUBA улучшена производительность за счет отказа от рефлективных вызовов в пользу генерации лямбда ...