«Мы даже не пытаемся запустить старый код, такой задачи у нас не стоит в принципе» — Роман Елизаров о разработке Kotlin

в 13:56, , рубрики: coroutine, java, joker, jvm, kotlin, techtrain, Блог компании JUG.ru Group, Программирование, Спортивное программирование

Если хочешь в чем-то разобраться — учись сразу у лучших. Сегодня на мои вопросы отвечает бог корутин и concurrency, Рома elizarov Елизаров. Мы поговорили не только о Kotlin, как вы могли бы подумать, но ещё и о куче смежных тем:

  • Golang и горутины;
  • JavaScript и его применимость для серьезных проектов;
  • Java и Project Loom;
  • олимпиадное программирование на Kotlin;
  • как правильно обучаться программированию;
  • и другие волнующие вещи.

«Мы даже не пытаемся запустить старый код, такой задачи у нас не стоит в принципе» — Роман Елизаров о разработке Kotlin - 1

Kotlin — молодец!

Привет. Давай вначале пару слов о себе. Ты давно занимаешься Kotlin?

У меня с Kotlin давняя история. В 2010 году Kotlin начинался как проект в JetBrains, где я в тот момент еще не работал. Но Макс Шафиров (он тогда занимался Kotlin и был одним из инициаторов этого движения внутри JetBrains) пригласил меня стать внешним экспертом и посмотреть на дизайн, прокомментировать. Изначально язык дизайнился для решения своих проблем, ведь у JetBrains своя большая база кода на Java, с понятными проблемами, которые в коде постоянно есть, и хотелось сделать язык для себя, чтобы свой код писать приятней, эффективней, с меньшим количеством ошибок. Просто провести у себя модернизацию. Естественно, это быстро переросло в идею, что раз у нас такие проблемы — значит, и у других такие проблемы есть, и им нужно было подтверждение от других людей, что они идут правильным путем.

Меня пригласили как эксперта, чтобы я посмотрел и сверил то, что происходит, с тем, что надо. Про nullability — это я настоял, что этим надо заниматься, потому что мне в тот момент было очевидно, что если ты пишешь на Java, там много проблем, но nullability — это основная беда, на которую постоянно наталкиваешься.

В самой работе команды я не участвовал, просто периодически поглядывал, участвовал в соревнованиях на Kotlin (Kotlin Cup). Я всю жизнь занимаюсь соревнованиями, но сам уже тогда активно не участвовал. Например, я бы не вышел в финал соревнований вроде Facebook Hacker Cup, форма не та из-за того, что в соревнованиях уже не участвую на постоянной основе. А в Kotlin Cup я принял участие и, так как он не собрал широкую аудиторию, я легко вышел в финал.

На тот момент (2012-2013 гг.) Kotlin представлял собой грустное зрелище с точки зрения тулинга, потому что там всё тормозило. С тех пор команда проделала огромную работу. Я пришел в команду два года назад, сразу после релиза 1.0 и до того, как Google официально признал язык. В команде я занялся всякой асинхронностью и корутинами, просто потому что так вышло, что у меня подходящий опыт, я много в DevExperts занимался всякими разными большими энтерпрайзными системами, и там много асинхронности и коммуникации. Поэтому я хорошо представлял себе проблемные места — что надо чинить и что у людей болит. Это очень хорошо легло на нужды Kotlin, потому что болит не только у нас. Болит у всех. Даже в JVM занялись Project Loom, что как бы намекает, что болит у всех. Я до сих пор занимаюсь котлиновскими библиотеками, и основной наш фокус — на всякие connected-приложения и асинхронность.

То есть ты занимаешься в основном библиотеками, не компилятором и вот этим всем?

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

Получается, если зайти в YouTrack, пофильтровать по тебе, можно много чего интересного обнаружить.

Да, можно найти кучу всяких задач, потому что я постоянно на что-то наталкиваюсь.

Ты упомянул Project Loom. Его сделал парень, который сделал Quasar. Cо стороны это выглядит очень забавно, я как раз хотел на Хабру писать статью про Loom. Можешь рассказать что-нибудь про него?

Видел презентацию, идея понятная. Корутины и асинхронное программирование нужны всем. Например, на прошлом JPoint ребята из Alibaba рассказывали, как они хакнули JVM и прикрутили себе файберы хотспот, просто накатив туда патчик, который даже не они написали, а какие-то ребята до них. Они уже потом подпилили под себя. Замечательный доклад. Очень рекомендую.

А ты рекомендуешь так делать?

Так делать в энтерпрайзах приходится. Каждый большой энтерпрайз, выше какого-то размера, когда у тебя начинает работать несколько тысяч человек (а для кого-то и меньше), мейнтейнит свой хак OpenJDK. И конечно, если у тебя есть бизнес-критичные юзкейсы, то почему бы и не [акнуть что-то под себя, не вижу в этом никакой большой проблемы. Не то чтобы я это рекомендую, но приходится. Если в HotSpot нет легковесных потоков, то что делать? Это, собственно, говорит о том, что людям надо, что назрело. И фидбэк, который мы получаем по корутинам, тоже говорит о том, что да, назрело, людям нужны легковесные потоки, у людей вагон юзкейсов для легковесных потоков. Тот факт, что они должны как-то поддерживаться в JDK, давно назрел, и в этом смысле я не сомневаюсь, что когда Loom рано или поздно дойдет по продакшна, это будет востребовано. Есть люди, которым это надо. Есть люди, которые даже ради этого патчат HotSpot.

Видел, частая проблема — у тебя есть какой-то веб-сервер, в него много людей стучится, и он начинает блокироваться на тредах.

Это довольно типичная проблема. И веб-сервер, и application-сервер, и бэкенд. Если ты посмотришь ту же презентацию Алибабы, почему и понадобилось это дело, то у них не веб-сервер, у них классическая энтерпрайзная архитектура, у них на бэкенде на Java написаны всякие сервисы, эти сервисы находятся под нагрузкой. Я с таким же работал в DevExperts: сервисы под нагрузкой, тебе приходят запросы, которые ты не сам ведь обрабатываешь — в современном мире у тебя всё connected. И вот этот запрос ты не сам обрабатываешь, а еще 100500 всяких других сервисов вызываешь и ждешь, пока они ответят. И если эти сервисы тормозят, то у тебя много потоков ждет. Ты не можешь себе позволить иметь десятки тысяч этих ждущих потоков. И у тебя получается просто из-за какой-то ерунды следующее: один сервис, который ты используешь, тормозит, и куча потоков стоит и ждет. И сейчас это очень большая проблема.

Одна из причин, почему люди массово мигрируют на Go — не потому, что язык хороший, а потому что там легковесные потоки из коробки, и такой проблемы уже нет: горутины могут ждать, и они ничего не стоят. В том же Alibaba, решение, которое они заимплементили — оно вообще тупое из всех тупых. Они не очень легковесные в том смысле, что они каждой корутине выделяют один большой стек по 2 мегабайта, хакнув HotSpot, чтобы можно было эти стеки переключать. Они экономят физический поток, но не экономят стеки. И для них решение работает — оно, кстати, очень простое, у них патч HotSpot, насколько я понимаю, не очень большой. Ребята из Loom затеяли нечто более глобальное. Они решили сэкономить не только на физических потоках, но и на стеке, чтобы не тратить 2 мегабайта на поток. В прототипе текущий стек через HotSpot проходит, его копируют в маленькую хиповую структуру. И могут дальше этот физический стек переиспользовать для других целей.

Но там есть такой хитрый хак: когда ты возвращаешься назад на исполнение, они копируют его не весь, а только самый верх.

Да, там вагоны хаков и оптимизаций. Что в итоге из этого получится — очень сложно сказать. Потому что на примере подхода с копированием, сразу возникает следующая проблема: а что делать с нативными вызовами? Изнутри нативного вызова ты уже не можешь скопировать стек нативного вызова. В подходе Alibaba такой проблемы нет. Нативный, не нативный — какая разница, ты просто тот стек отцепил совсем и оставил его в покое, подцепил другой стек, всё работает. И тут рано говорить, что получится или не получится, с этим нативным стеком иногда можно жить, иногда нельзя — на этом этапе рано сказать. Например, как это в Go реализовано — там совсем другой механизм. Пока ты выполняешь гошный код, используются маленькие гошные стеки. Соответственно, когда гошный рантайм вызывает функцию, он смотрит, сколько нужно стека. Если текущего стека не хватает, он перевыделяет — увеличивает размер выделенного стека. Если, соответственно, ты делаешь нативный вызов, то они уже берут какой-то большой нативный стек из некоего пула и используют его.

И для гошного кода тоже?

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

Нам постоянно задают вопрос: «Что быстрее? Что подходит? Как вы в корутинах это делаете?» Мы в корутинах не хакаем JVM. Наша задача заключается в том, чтобы это работало под обычным JVM. И чтобы на Android тоже работало. Там свой ART, который тоже о корутинах ничего не знает. И поэтому, естественно, нам приходится ручками генерировать байткод, который делает что-то очень похожее на копирование стека, который делает Loom, только мы это делаем в байткоде. Берем его, когда он уже засаспендится. Берем стек, разматываем и копируем в хип. Мы не на рантайме, который бы это за нас делал, у нас сгенерирован байткод, который это делает. Он сохраняет и восстанавливает состояние корутины. Из-за того, что мы не делаем рантайм, естественно, у нас от этого больше оверхеда. В рантайме ты можешь все сделать быстрее. С другой стороны, если ты корутины используешь для асинхронного программирования, то тебе надо заснуть, если ты ушел ожидать ответа от какого-то сервиса, а послать запрос в какой-то сервис так дорого, что весь оверхед на копировании стека вообще никого не волнует — медленный он у тебя или быстрый — вообще становится неважно. Да, если ты это используешь именно для асинхронного программирования. У нас на корутинах в Котлине это замечательно скейлится, как и показано в прототипе Project Loom.

Другое отличие — так как мы в Котлине вынуждены делать это в байткоде, то у нас есть такой интересный побочный эффект. С одной стороны, вроде бы и неудачно, а с другой — наоборот. Заключается он в следующем: нельзя усыпить произвольную функцию. Нужно функции, которые могут заснуть, помечать модификатором suspend — явно пометить, что функция может приостановиться и чего-то ждать, что она долгая. С одной стороны, в Loom тебе это не нужно, потому что рантайм может усыпить что угодно. В решении от Alibaba то же самое — ты можешь у любого потока отобрать стек. Или в Go — там всё можно засаспендить, любой код может уснуть. Наплоди еще горутин и делай. С одной стороны, этот подход очень похож на программирование с тредами. Ты как бы программируешь как раньше, только теперь треды называются файберами и стали очень дешевыми. Если внимательно посмотреть презентацию того же Loom, выясняется, что файберы и треды — это всё-таки разные вещи. Как сделать так, чтобы старый код, который написан с тредами, прям совсем из коробки завелся на файберах — не очевидно, и что у них получится — никто не знает. Там начинаются проблемы: а что делать с дэдлоками, что делать с кодом, который соптимизирован на thread locals, опять же какие-то хэши свои локальные имеет или по thread ID хитро какие-то перформанс-оптимизации делает. И в Go та же самая проблема — когда хардварные thread ID не экспозятся, писать какой-то high performance-алгоритм становится нетривиально.

А в Котлине такого нет?

В Котлине мы же не пытаемся сделать вид, что тред и файбер — это одно и то же. Мы даже не пытаемся запустить старый код, такой задачи у нас не стоит в принципе. Мы говорим: «Извините, так как мы не рантайм, мы не можем произвольно взять старый джавовый код и начать там что-то переключать». И даже не будем пытаться. У нас другая задача. Мы говорим, что у нас есть фича языка, засыпающии функции, вы можете с ними писать асинхронный код, и это новая фича языка. И от этой проблемы («как запустить старый код») мы таким образом полностью дистанцируемся, мы говорим: «Вот есть новый код, хороший, православный, его можно усыплять». В какой-то степени это делает жизнь проще, потому что не надо парить голову ни себе, ни людям, а что происходит, если какой-то старый говнокод, который не знал, что его будут на файберах запускать, вдруг на них запустят.

У нас в нашей модели нет никакого старого кода, только новый, который изначально готов к тому, что сегодня он на одном треде, завтра на другом, и если ему, например, нужно узнать, какой сейчас тред, он это узнает. Да, нужен thread local, но он может их узнать. Однако он должен быть готов к тому, что сегодня thread locals одни, а завтра — другие. Если он хочет, чтобы эти локалы путешествовали с ним, для этого есть другой механизм, корутинный контекст, где он может хранить свои вещи, которые будут вместе с корутиной путешествовать с треда на тред. Это, в каком-то смысле, нам упрощает жизнь, потому что мы не пытаемся старый код поддерживать.

А с другой стороны, мы заставляем человека явно подумать над своим API, сказать: вот я пишу функцию на Kotlin с корутинами. Если раньше я смотрю на какой-то метод в своем коде, getЧтоНибудь, непонятно, этот метод быстро работает и возвращается сразу или пойдет в сеть и может час работать — я могу только документацию почитать и понять, как быстро он будет работать. А может, сейчас он быстро работает, а завтра придет программист Вася Пупкин и сделает так, что он теперь ходит в сеть. С Kotlin-корутинами мы даем гарантированный языком механизм с модификатором suspend. Я когда сам работаю с корутинами, смотрю на какую-то функцию, если не вижу модификатора suspend, значит, она быстро работает, всё локально делает. Есть модификатор suspend, значит, эта функция какая-то асинхронная, она пойдет надолго в сеть. И это помогает делать самодокументирующийся API, чтобы мы сразу видели, что нас ожидает. Это помогает сразу избежать тупых ошибок, когда я где-то забылся и где-то в коде вызвал что-то долгое, не подозревая об этом.

На практике это оказывается очень хорошо. Вот эта необходимость явно разметить эти засыпающие функции. В Go, например, этого нет, я не обязан там ничего размечать. Выясняется, что этот побочный эффект нашей имплементации (что надо размечать модификатором suspend) помогает тебе сделать правильно архитектуру, помогает тебе контролировать, что ты не вызовешь какую-то случайную дико долгую асинхронную дичь в месте, где ты изначально ожидал, что всё произойдет быстро.

Но есть же часть вещей, которые сложно запретить, например, какой-нибудь сетевой IO, файловый.

Нет, сетевой IO как раз запретить достаточно легко. Вот файловый IO — сложно. Но здесь опять тонкий момент: для большинства приложений файловый IO — это быстрая вещь, и поэтому совершенно нормально, что он работает синхронно. Очень редкое приложение так много работает с IO, что для него становится проблемой тот факт, что это занимает так много времени. И здесь мы даем человеку возможность выбрать: ты можешь у нас напрямую делать файл IO и не париться, потому что оно будет блокировать, что происходит (потому что обычно это быстро). Но если конкретно в твоем кейсе просто какое-то очень долгое вычисление, вроде не асинхронное, но просто жрет кучу времени CPU, и ты не хочешь этим самым блокировать какие-то свои другие thread-пулы, мы предоставляем простой понятный механизм: ты заводишь отдельный thread pool для своих тяжелых вычислений, и вместо того, чтобы писать обычную функцию, которая fun computeSomething(), и писать в документации «Чуваки, аккуратно, эта функция может работать очень долго, поэтому внимание — не используйте ее где попало, не используйте в UI», мы предлагаем более простой механизм. Ты просто пишешь эту функцию как suspend fun computeSomething(), а для её реализации используешь специальную библиотечную функцию withContext, которая перекидывает вычисление на указанный тобой специальный thread pool. Это очень удобно: пользователю не надо больше парить мозг: он сразу видит suspend, знает, что этот вызов его тред не блокирует, и он может совершенно спокойно вызывать её из UI-потока и так далее.

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

Я думаю, насколько это безопасно. Может ли кто-то сломать thread pool или вломиться в чужие данные?

Естественно, всё возможно. От кривых рук тяжело защитить. Понятно, что сколько бы мы ни писали в компиляторе всякие системы типов и проверки, всегда всё можно сломать. Вопрос в том, что компилятор должен помогать писать правильный код. К сожалению, мечта запретить писать плохой код утопична. Мы специально не включаем какие-то фичи в язык. В Котлине нет каких-то вещей из Java, если про них известно, что они в основном используются не по назначению и код с ними в основном плохой. Но и любую хорошую фичу, которая есть в Котлине, можно использовать не по назначению массой разных способов. Вариантов нет, к сожалению. Язык можно абьюзить по-разному. От кривых рук не защитишься никак.

Я узнавал у изучающих Kotlin, какой интересный вопрос можно тебе задать. Они сдались и сказали, что ты очень хитрый и от их вопросов гибко уходишь.

От каких вопросов?

Например, один вопрос пережил двух человек: почему в Котлине нет raw types? Без дженериков.

Потому что всегда можно написать звёздочку.

А оно при этом будет совместимо с Java?

Да.

То есть у тебя есть какой-то джавовый метод, который требует что-то без дженерика. List, например.

Если есть такой джавовый метод без дженерика, ты туда можешь передать любой List. Можешь List со звёздочкой передать, можешь со строкой. Котлин позволит тебе в джавовый метод передать любую дичь. Если он возвращает тебе raw List, то в Котлине ты получишь некий platform type, который ты можешь закастить, например, к List со звездочкой. Потому что raw type, по сути, в Java сделан не от хорошей жизни, а чтобы было проще мигрировать. Была Java, в которой не было генериков, теперь есть генерики, и чтобы люди не мучались, чтобы им не нужно было по всему коду эти угловые скобочки проставлять, была сделана эта специальная фича, raw type. Когда делали Котлин, такой проблемы не было — не было никакого старого кода, в котором генерики не указаны. Есть только новый код, в котором все типы — генерики. Поэтому проблема миграции со старого кода без генериков на новый код с генериками — её не существует, и нет смысла делать в языке такую фичу, как raw types. Она Котлину не нужна, так как тут нет migration story.

Как работает кастинг platform type к котлиновскому?

Платформенные в Котлине считаются flexible. Это проще рассмотреть на примере nullable-типов. Вот у тебя есть джавовский код, который возвращает String. В Котлине непонятно, там String или nullable String — это два разных типа. И мы ведь не знаем, какой String возвращается из джавовского метода — nullable или не-nullable. Поэтому Kotlin считает, что он может быть и такой, и сякой. И разрешает тебе его присвоить как в String, так и в nullable String. То же самое и здесь, когда ты получаешь какую-то слаботипизированную дичь от Java, ты на стороне Котлина всегда можешь указать просто более специфичный и правильный тип.

А если ты указал неправильно?

Если ты указал совсем неподходящий, то у тебя компилятор, естественно, ругнется, потому что он должен подходить. Flexible не значит что угодно, это не dynamic. Flexible указывает на диапазон — то, что сейчас вернула Java, ты потом можешь присваивать и в String, и в nullable String, но не в int.

Там всё-таки есть какое-то прямое перечисление возможного, и оно там матчится.

То же самое с сырыми типами, похожий механизм, но немного посложнее. То же самое с коллекциями. В Котлине коллекции разделяются на read only и mutable. У тебя бывает List и MutableList. Если тебе Java возвращает List, то фиг поймешь, что Java-программист имел в виду, можно мутировать его или нельзя, поэтому ты уже на Котлин-стороне можешь в List присвоить или в MutableList. Котлин более строго типизирован, чем Java. Соответственно, когда ты из Java что-то получаешь, ты должен более специфичный тип указывать и наоборот. В обратную сторону работает из коробки, так как джавовский метод принимает менее типизированные вещи, туда можешь любую подходящую котлиновскую штуку передать. Опять же, принимает, например, джавовский метод List, мы же не знаем, какой, поэтому можно туда и MutableList, и обычный List передать. А если ты будешь передавать не List, а String, то не разрешит.

А когда используются джавовские либы какие-то либо, какой правильный подход? Надо заворачивать в какие-то врапперы котлиновские?

Не, ничего не надо заворачивать. Котлин задизайнен так, чтобы джавовские библиотеки было просто использовать. It just works. Большинство Java библиотек просто работают с Котлином вообще без проблем. А если там еще nullability-аннотации прописаны, то Котлин сразу видит, nullable или нет результат. Но удобство зависит от дизайна библиотеки, конечно. У Котлина, например, специальный синтаксис, когда последнюю лямбду удобно передавать за круглыми скобочками. Например, в джавовых либах, у которых такой же порядок аргументов, понятно, что последним аргументом принимается какой-нибудь предикат. В Котлине ты можешь использовать его без всяких специальных адаптеров, просто можешь использовать красиво по-котлиновски. Берешь какую-нибудь JavaFx, и без каких-либо дополнительных адаптеров код на Котлине получается красивее, чем если бы ты JavaFx использовал на Java, удобнее, приятнее его смотреть. Понятно, что ты можешь еще написать себе каких-то адаптеров, и это будет еще круче. Но даже без них джавовские либы приятно использовать. Любая либа становится круче и удобнее, если ты просто начинаешь использовать её из Котлина, просто по факту использования Котлина.

У тебя аж голос изменился, так эмоционально всё описываешь.

Конечно. Я просто видел это много раз, тебе просто так приятнее программировать. Мы на такое же надеемся и с Kotlin Native. Идея та же: Котлин приятнее как язык, и мы там пытаемся сделать максимально seamless интероп со всякий C-шной экосистемой, просто чтобы дать людям возможность использовать их существующие либы из более приятного языка, без каких-либо барьеров.

Кстати, а ведь при переходе на Native там не должен ли измениться смысл языковых конструкций?

Конечно, у нас, даже если посмотришь на Kotlin JavaScript, какие-то конструкции немного по-другому работают. Мы не ставим целью добиться «Write once, run anywhere», не стоит такой цели, чтобы было абсолютно идентично. Наша задача немного другая: мы хотим некое подмножество языка, вроде Common Kotlin или Portable Kotlin, на котором ты будешь писать, и оно будет работать отовсюду. Понятно, что ты можешь залезть в какие-то платформо-специфичные штуки, и их поведение будет соответствующим, но это нормально. Если ты пишешь под одну платформу, и тебе всё равно, а под несколько платформ ты просто какие-то вещи будешь обходить стороной. И мы многие вещи специально не фиксим, чтоб сохранить перформанс.

На JVM у нас перформанс, как у Java, на JS у нас перформанс почти такой же, как у JS, на Native нативный перформанс. Задача, в первую очередь, не смотря на эту переносимость, дать нативной платформе высокопроизводительный код, а не писать еще одну виртуальную машину. Многие пытаются транспилировать какую-нибудь джаву в тот же JS, попытка полностью эмулировать джаву. Эта попытка приводит к огромному перформанс-оверхеду. Какие-нибудь банальные глупые вещи: ты берешь double, конвертируешь в String. И на JVM-ном Котлине число 0 превратится в строку «0.0», а на нативном JS будет просто ноль. Разное поведение. Можно было бы на JS это пофиксить, но тогда все преобразования чисел в строки стали бы намного тормознее, потому что он будет обвешан дополнительными проверками — кому это надо? Пускай будет эта небольшая разница, зато нативный перформанс. У нас нет никакой своей специальной тормозной функции, которая преобразовывает числа в строки, у нас просто нативное JS-ное преобразование. Таких примеров очень много, где мы специально принимаем решение сделать разное поведение из перформанс-соображений. Но там, где это не критично. Всё-таки семантика основных конструкций языка — классы, наследование, вызовы и так далее — все работает так же. У нас есть пример огромных проектов, как собственных, так и внешних, в которых написано много кода на Котлине, он компилируется и работает под JVM, JS, Native — и всё это нормально работает, даже несмотря на то, что где-то поведение немного отличается. Все тесты проходит.

А корутины работают на всех платформах?

Да. Это фича языка, которой всё равно, под какую платформу ты её запускаешь.

То есть вся размотка стека, вот это всё…

Да, это всё исключительно компиляторная фича. Нам ведь не нужна поддержка от платформы, вот в чем фишка. В отличие от проекта Loom и так далее. Мы не делаем это каким-то хаком в JVM. Это фича компилятора, поэтому мы можем то же самое сделать под любую платформу.

А если взять котлиновский код и попытаться его зареверсить, например, в Java?

То ты увидишь там всю эту дичь, которую компилятор выдал. Зато у тебя красивый код на входе. В смысле — зато ты написал красивый код. Это уже задача компилятора сделать из этого нечто, что будет работать. В Котлине много конструкций высокого уровня, которые потом превращаются в какую-то низкоуровневую пургу. Например, пишешь for ( i in 0..10 ), и это разворачивается в цикл for (int i = first и прочую дичь. Но писать приятно, так что какая разница, как оно там компилируется. Оно работает. Быстро, потому что разворачиваются в соответствующие нативные конструкции.

А кто-нибудь сравнивал одни и те же программы на разных платформах?

Нет, и более того… несмотря на цель сохранить перформанс, платформы изначально несравнимы. У них разные задачи. Какой смысл сравнивать JVM и JS.

А какой смысл писать на сервере на Node.js?

Так понятно зачем! Не ради перформанса. Люди пишут для того, чтобы реюзать своих JS-программистов. Зачем изучать новый язык, новых программистов нанимать, если JS-программисты спокойно пишут бэкенд. Мы такую историю хотим дать с более хорошим, типизированным языком. Если ты умеешь программировать на Котлине, то один раз написал бизнес-логику и дальше пожалуйста — гонять ее на джавовом бэкенде, годняй на JS-фронтенде, гоняй в нативном микросервисе или чем-то — пофиг. Если код на Котлине, он сможет скомпилироваться куда угодно. В этом как раз цель Котлина.

У тебя были доклады вроде «миллиона котировок». Ты в своих предыдущих проектах стал бы использовать Котлин, если бы он был изобретен сильно раньше?

Конечно. Всё, что я говорил в те времен, а можно делать на Котлине. На JVM это просто более удобный язык, чем Java, компилирующийся в тот же самый байткод. Поэтому не использовать его на JVM большого смысла нет. Разве что у тебя legacy, enterprise, и это просто запрещено. Или если ты делаешь библиотеку, которую должны использовать клиенты, которые Котлин не могут использовать. А если ты пишешь для себя, нет никакого смысла не писать на котлине под JVM. Байткод тот же самый, а на входе — не только более удобный, но и более компактный язык. Он еще и более типизированный, защищает от большего количества ошибок. Но не заставляет писать совсем жестко типизированную дичь, — как всегда в JVM можно сказать компилятору «я знаю лучше тебя». Механизмы обойти компилятор у тебя есть. Но в обычной практике API на Котлине получаются более документированными, более строгими и более безопасными. Код получается надежней, реже падает по исключениям. Его читать проще, меньше воды в коде. Многие штуки, когда в Джаве пришлось бы писать бойлерплейты, в Котлине пишется в одну или несколько строчек. С таким кодом приятней работать, в нем меньше воды и больше сути, которую ты хотел выразить.

В эти выходные Роман будет на фестивале TechTrain с докладом «Зачем нужен еще один язык программирования?». Об этом фестивале мы совсем недавно писали на Хабре. Загляните, вдруг понравится.

Обучение и олимпиады

Раз уж ты начал говорить про JS и обучение, насколько сложно на Kotlin переучиться с Java?

Вот с Java как раз очень легко и вообще никаких проблем. У нас есть и книжка «Kotlin in Action», и сайт, ориентированный на Java-программистов. По опыту, Java-программисту нужно потратить от двух дней до двух недель, и всё, вышел из тебя отличный Kotlin-программист. И это не случайно получилось, это «by design». Изначально в дизайне Котлина заложено, что Java-программистам должно быть легко на него перейти. Велосипед не изобретали. У Андрея Бреслава на прошлом JPoint есть хороший доклад, откуда что Kotlin позаимствовал. Есть слайд о том, откуда родились разные языковые конструкции. Видно, что 60-70% взялись из Java. Оно и называется как в Java, чтобы было проще, чтобы не нужно было изучать что-то сильно новое. Это большие языки типа Java могут себе так позволить.

У них вообще есть такая дизайн-цель — об этом еще Гослинг говорил, что если мы что берем, то обязательно называем по-другому. Никогда ничего не берется as-is. Мы люди большие, можем себе позволить, пускай люди учат. В Kotlin же, если ты уже знаешь, что такое «класс», это и должно называться «классом». Если знаешь, что такое «интерфейс» — должно называться интерфейсом. Люди знают цикл while — нет смысла его переименовывать. Хотя можно найти 100500 более хороших названий для него. Но зачем? Кроме того, большинство наших обучающих материалов рассчитано на Java-программистов. Даже я делал какие-то доклады, «Введение в Kotlin», и все это расчитано на них, нужно рассказать только какие основные вещи в Котлине новые и интересные.

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

Для таких новичков, что совсем-совсем, или для перебежчиков с других языков?

Да, для совсем новичков. Еще стараемся сотрудничать с вузами. На пути переучивания Java-программистов мы очень далеко продвинулись. Android-программисты тоже — больше половины, вроде бы по последним данным. И все переучились без проблем. Миллион программистов как минимум уже переучились, и никаких проблем нет. А вот на обучении с нуля — мы находимся на очень раннем этапе пути.

А есть какие-то шутки, которые людям сложно понимать с нуля?

Они такие же, как в других языках. Людям сложно понимать вложенные циклы, рекурсию, референсы. Это известная тема, неважно, на каком языке ты программируешь. Обучение человека с нуля — это некое искусство, там есть сложные моменты, которые надо адекватно объяснять. Но в этом плане Kotlin очень хорош. Например, мы получаем фидбэк от университетов. Если взять базовый курс программирования в каком-нибудь ВУЗе и посмотреть, чему их учат, окажется, что никто не учит сразу классам. Обычно учат простым процедурным вещам: как писать циклы, как писать функции вызывать и так далее. Раньше многие учили на C++, потом ринулись в Java, теперь на Python. Но не каждый язык одинаково хорош. Та же Java была когда-то очень популярна, и до сих пор многие вводные курсы читают на Java, но это не самый лучший язык именно для вводного курса программирования. Чтобы написать простой хэлловорлд, нужно написать класс, puiblic static void main… А ты же учишь не объектно-ориентированному, а процедурному программированию. В Java, чтобы что угодно написать, надо объявлять класс. Зачем новичку парить мозг?

Kotlin в этом плане больше подходит для обучения: открываешь файл, пишешь функции свои, почти как в Python, только с типами. По сравнению с C++ можно поспорить, потому что C++ очень большой язык. Это не значит, что на плюсах нельзя учить программистов, можно. Но когда учат на плюсах, то учат очень ограниченному подмножеству. Рассказывают небольшие фишки, очень аккуратно, чтобы обучающийся не сделал шаг влево и шаг вправо. А Kotlin — хороший типизированный язык. Если хочешь хорошему нетипизированному языку научить — это Python. Я свою дочку в качестве первого языка программирования научил Питону. Чтобы не забивать ей голову типами сразу. Когда ты вообще не умеешь программировать, тебе нужно очень много узнать. Не бывает так, чтобы ты сразу всё узнал. Нужно постепенно учить. Поэтому проще вначале научить всяким императивным конструкциям — циклы, ввод, вывод, функции, процедуры — а типы отложить пока в сторонку. Следующий же язык должен быть типизированным, чтобы разобраться с типами.

То есть ты считаешь, что типизация — полезная штука?

Я не просто считаю, что она полезная. Это must have. Документацию никто не пишет в своем коде, а даже если пишет — не мантейнит и не читает. Поэтому без типов никакая разработка не масштабируется. В одиночку еще можно программировать на нетипизированном языке, программки до десяти тысяч строчек от силы. Десять тысяч — уже тяжело. А если у меня большой проект, много разработчиков, это просто неподдерживаемо, ничего не понять.

А как джаваскриптеры живут?

Ну как живут… подевелопили проект, бросили, пошли к следующему. Так и живут. Большинство JS-проектов — очень маленькие. Сделал — и всё. Есть большие проекты на JS, но там не живут, а страдают. И если кто-то пытается делать большой проект на JS, скорей всего переходит на type checkers, на Flow или TypeScript. Что-то большое поддерживать на JS сложно именно по причине отсуствия типов. То же самое с Python. Пока это какие-то шаблонные DSLки, всё отлично. Ты можешь на Django зафигачить огромный проект, если ты сильно не изобретаешь велосипед. Пишешь джанговские классики, пользуешься обычными механизмами. Когда я говорил про сложность проекта в строчках, это, конечно же, обман. Сложность не определяется строчками кода. Я могу иметь огромнейший сайт на Django, где огромное количество строчек кода, десять тысяч формочек и CRUD-страниц. Но так как все они одинаково нафигачены, бизнес-логики нет — это всё легко и понятно. Но как только я начну писать сложную бизнес-логику, делать какую-то иерархию классов, моделировать сложный домен, то в нетипизированном языке я очень быстро умру. Очень быстро. И всё это поддерживать будет невозможно. На этом строится философия Kotlin, он еще более типизированный, чем Java, еще более строгий. Это core belief в команде Kotlin, что язык промышленного масштаба должен быть строго типизированным.

Правильно понимаю, что если есть какой-то большой сложный фронтенд, то даже там Kotlin уже имеет смысл?

Конечно! Если фронтенд большой и сложный, значит, в нем вряд ли просто какие-то CRUD-странички. Опять же нужно различать: если у тебя большой и сложный сайт в котором много страниц, и все страницы одинаковые, и логики там нет — вьюшки, то это одно. А вот если это большое сложное веб-приложение, в нем много сложной логики — конечно, нужен типизированный язык. Это объясняет, почему TypeScript и Flow набирают популярность — это типизированные штуки поверх JS.

Про Kotlin есть еще момент, что Kotlin для JS не может победить TypeScript просто так, один на один. Если ты будешь сравнивать разработку веба на TS и Kotlin/JS, естественно, TS победит, потому что он заточен под JS-экосистему, он прямо для нее создан. Зато Kotlin/JS победит, если тебе надо этот код шарить. Можешь и на фронтенд скомпилировать, и на бэкенд.

И там будут всякие заточки, о которых ты говорил — вроде конвертирования double…

Да, но это на практике всё довольно минорные различия. Мы очень аккуратно думаем над тем, где мы можем позволить разному поведению быть, а где нет.

Когда-то ты был связан с олимпиадным программированием.

Я до сих пор связан.

А чем ты занимаешься?

Провожу олимпиады :-) И сам участвую, но редко.

Просто я вижу, что на олимпиадах иногда доступна Java в качестве одного из основных языков.

Сейчас она доступна практически всегда. Она появилась лет, наверное, пятнадцать назад. Java и C++ — это два стандартных языка, которые все поддерживают, а дальше — вариации, в зависимости от соревнования.

А на Java сложней выиграть, есть какие-то скрытые оверхеды?

Зависит от соревнования. В нормальном соревновании — одинаково, если в нем задачи больше на идею и правильный алгоритм. Но бывает какая-нибудь дичь, когда задачи подразумевают неасимптотическую оптимизацию, где надо всё до такта оптимизировать — там, конечно, на Java будет тяжело, придется много стараться. Плюс бывает очень маленькое время выполнения теста. Грубо говоря, если у тебя ограничение по времени исполнения несколько секунд, то HotSpot за секунду прогревается на небольшом коде и пофиг. А если у тебя лимит на все — секунда, то на Java ты можешь проиграть просто за счет того, что пока HotSpot разогревается и компилируется — уже секунда прошла.

Да, бывают дикие соревнования, где на Java тяжело. Но нормальные соревнования (популярные, поддерживаемые хорошими людьми) — там стараются сделат задачи и окружение так, чтобы на Java и на плюсах были одинаковые шансы. И причины понятны: хоть Java и не растет в образовании, но и сильно никуда не убывает. Где-то некоторые вузы отказались учить Java и перешли на Python — и из-за этого, в том числе, сейчас многие соревнования научились Python. Это такой стабильный третий язык из поддерживаемых. Соревнования, в основном, студенческие. Есть и профессиональные соревнования, и большие компании делают что-то вроде Facebook Hacker Cup, где может каждый участвовать, но всё равно, основная тема в спортивном программировании — школьная и студенческая. В школьные и студенческие годы люди будут постоянно выступать и тренироваться. Но после выпуска из ВУЗа, после выхода на работу — очень мало людей будут продолжать участвовать. Поэтому выбор языков определяется тем, что используют в образовании. Если учат плюсам, яве и питону, то и на соревнованиях будут они. Для многих программистов Java — первый язык, соответственно, все соревнования стараются поддерживать Java. Ради соревнований учить С++ — дичь. Он для системного программирования, низкоуровневого программирования, тебе не нужно иметь миллион C++-программистов, это бессмысленно совершенно.

А как тебе идея — добавить Kotlin в список стандартных языков?

Ну вот, собственно, эту идею мы активно и продвигаем. Есть ICPC, который ежегодно проходит, собирает сотни тысяч участников по всему миру, больше сотни команд проходит в финал. В ICPC Kotlin поддерживается. Сейчас там список языков такой: C/C++, Java, Python и Kotlin. Но пока, естественно, на нем никто особо не пишет, по причине вот какой проблемы: проникновение в образование еще на очень раннем этапе. На студенческих соревнованиях используются те языки, которым студентов учат.

А где-нибудь уже учат Kotlin?

Где-то точно учат. Например, в Питерском Политехе. Но мы пока на очень раннем этапе, на «шаге 0» этого процесса.

Там нет каких-нибудь фатальных недостатков?

Нет, для начального образования Kotlin лучше, чем остальные языки. Просто образование — консервативное. У людей есть готовая программа, учебники. Никто не любит изменений. Зачем профессор, который учит на первом курсе студентов программированию, будет менять язык, в чем бонус? Это может раз в десять лет пересматриваться.

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

Нет. Потому что не так важно, какой язык ты изучил первым. Профессиональный программист за свою жизнь изучает десяток языков и использует около трех языков активно. Плюс всё это постоянно меняется. Чему тебя научат программировать первым — не так важно. Важно, какой ты багаж языков имеешь по выпуску из вуза — это другая тема, это важно. И здесь мы сталкиваемся с проблемами на консервативных рынках, которые ориентированы на авторитет. Например, в Китае существует проблема, которая выясняется после общения с ребятами оттуда. Берешь какую-нибудь крупную контору, в которой много программистов, спрашиваешь — почему вы не используете Котлин? А потому что вот, не учили ребят Котлину в ВУЗе, и они ничего нового не хотят изучать, а зачем им?

А у нас не так?

Это всюду так, просто в разном масштабе. В разных культурах по-разному. Есть культуры, в которых как гуру сказал, или как учитель сказал — так и будешь делать. Где-то люди более самостоятельные, больше склонны к экспериментированию, инновациям. Где-то люди пойдут и сами все изучат. Где-то пальцем не пошевелят и будут делать ровно то, чему их научили. В России внедрений Kotlin больше, но это и потому еще, что мы изначально отсюда, больше на конференциях выступаем и так далее.

Это в моем поколении программисты были энтузиастами. Я вырос, когда программировали те, кому это нравилось, они всё изучали самостоятельно, потому что ничего не было. А сейчас это массовая штука, которой учат. Возьми современного программиста, большая часть делает это не потому что любит, а потому что этому научили и теперь платят много денег. Соответственно, такие люди не будут изучать технологию, которая только что вышла. Зачем им?

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

Нет, конечно! На Котлине ты, скорее, получишь больше удовольствия.

Есть конкретные штуки, которые реально имеют бизнесовое значение — мы же говорили о переиспользовании между фронтом и бэком…

Не всем это нужно. С другой стороны, и удовольствие тоже. Не все от своей работы вообще получают удовольствие. Им платят деньги — они работают, какая им разница, получают они удовольствие или нет. Закончился рабочий день — закрыли и забыли об этом, и начали заниматься другими своими вещами.

Это как-то очень уныло, если не ужасно.

Это правда жизни, к сожалению. Какой бы ужасной она ни была. И таким людям, конечно, все равно. Kotlin, не Kotlin.

Насколько понимаю, в JetBrains как раз очень многие работают потому, что им нравится работать.

JetBrains в этом плане — нерепрезентативная выборка, естественно. Специально отобранные люди, мотивированные, которым действительно нравится вот это дело.

Наше время потихоньку подходит к концу, поэтому такой вопрос: можешь ли ты передать что-нибудь нашим читателям на Хабре? Какое-нибудь напутствие, какое-нибудь откровение?

Могу передать пламенный привет :-) А откровения никакого не скажу, какое может быть откровение? Единственный вывод, который можно сделать из нашего разговора: от работы счастлив тот, кто получает удовольствие. Я читал несколько блогов хороших людей, которые программировали на Джаве, просто работали, не получая никакого удовольствия. А потом по каким-то причинам им стало любопытно, жизнь заставила, они попробовали Kotlin, и неожиданно для себя открыли, что от работы можно получать удовольствие. Что можно любить то, что ты делаешь. Что можно любить язык программирования. А не просто использовать, безэмоционально, как некий инструмент. Конечно, язык — это некий инструмент, но можно относиться к нему опосредованно, а можно его любить. Это разное отношение, в том числе разное отношение к работе создает.

К Kotlin очень много людей испытывают теплые чувства, сравнимые с любовью, именно потому, что на Kotlin просто приятно программировать, особенно после Java. Может даже, не только после Java. Наверное, нет языков, на которых настолько приятно (именно такое слово) программировать. Есть языки с большей функциональностью, с более сильными фичами, есть языки с более строгой системой типов, есть языки, где все pure, есть, где всё наоборот — unsafe. Возьми любое измерение, и найдешь языки, которые в этом свойстве круче Kotlin. Но в Kotlin такой баланс, что неспроста он на StackOverflow в опросе этого года оказался вторым в топе most loved languages. Первым, кажется, стал Rust. Но Rust нам не конкурент, потому что Rust — язык системного программирования. Мы в эту нишу не лезем. Нисколько не обидно, что Rust в этом плане обогнал Kotlin. Мы боремся, чтобы Kotlin стал основным языком для прикладного программирования, на котором приятно решать прикладные задачи. Некоторых фичей Rust у нас нет и никогда не будет, потому что они просто не нужны прикладному программисту. Не должен он вручную управлять памятью или думать о тонкостях владения, прикладной программист должен решать бизнес-задачи. Он должен свой домен трансформировать в код. И это должно быть максимально прямое преобразование без каких-либо мешающих ему факторов. Мы пытаемся эти мешающие факторы устранить. Чтобы ты свою бизнес-задачу максимально прямо, без воды и лишнего кода преобразовывал в решение.

Ну это несколько нечестное соревнование — все эти языки вроде Java были много лет назад придуманы, а вы — только что.

Естественно, Kotlin учитывает опыт предшественников. Как и любой современный язык. Это и есть прогресс — когда что-то новое создается с учетом старых недостатков. Неспроста же в Kotlin сделаны nullable-типы. Ну что далеко ходить, возьми любой энтерпрайз, пойти в любую крупную контору, посмотри их крэш-логи, и увидишь, что самый частый exception — NullPointerException. Это известный факт, и если ты делаешь новый язык — нужно ее решать. Поэтому мы очень много внимания в языке уделяем nullability. И так далее. Если ты дизайнишь язык не абстрактно, не как академическое упражнение, а пытаешься решить проблемы людей, с которыми они сталкиваются часто, то язык получается хорошим. Почему его любят? Потому что он решает их проблемы.

Автор: olegchir

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js