Pull to refresh

Comments 128

Ktor, при всех его преимуществах, не достиг уровня фронтенд-фреймворка

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

Фронтенд фреймворка действительно нет такого, чтобы был сравним с react, angular, vue, хотя для первого есть офф. обертка. Имхо разработчикам языка надо больше времени уделять javascript, это может невероятно сильно популяризировать язык. Пока ощущения двояки, вроде работает, вроде есть минимизация stdlib, вроде есть транслятор типов из typescript, но когда подключаешь сторонние библиотеки на javascript, чувствуешь боль от того, что для каждой (99% из 100) нужно писатьгенерировать типы и весь этот boilerplate код таскать в одной папке с исходниками (который, кстати, устаревает быстрее, чем комментарии). По-сути это работа авторов библиотек, но они пока не заинтересованы в написании типов на kotlin, жалко
По-моему, он позиционируется больше как мультиплатформенный фреймворк для клиент-серверных приложений, и если есть что-то из фронтенда, то это только голый html, css

Ktor состоит из серверной части и клиентской. Клиентскую часть в очень сильном приближении можно рассматривать как попытку создания фронтендного фреймворка.

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

Там много еще недоделок, но дело движется…
Kotlin-React ничуть не хуже простого React, обёртки добываются из TypeScript-овских при помощи Dukat (да, увы, готовых нет и вряд ли появятся в ближайшем будущем).
В целом, на Котлине вполне успешно можно писать фронтенд, при этом используя общие с бэкендом классы модели и всякие там утилитные методы, типа валидаций и преобразований из варяг в греки.
Минус — жирный, даже при использовании DCE и сжатия, js рантайм, тут надо осторожно — сейчас это приемлемо для многих приложений, но не для всех.
общие с бэкендом классы модели

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

Да, конечно, речь идёт о DTO, которые летают между бэком и фронтом/мобилами (мы используем Kotlin MPP).
Мешает или помогает один язык и там и там — это вопрос, не имеющий, кмк, однозначного ответа. Такой язык как Котлин — нам помогает.
По моему опыту, на KMP удобнее делать интерфейсную логику доступа к бэку для фронта. Удобно же когда один раз сделал ее и разные фронты пользуются одним кодом.
Менее очевидно и более спорно использовать его для генерации внутренних моделей, которые могут включать логику. Например, на транспортном DTO расчет ФИО из Ф, И, О будет избыточным, но для внутренней модели это вполне нормально.
Ну и еще одно применение. Фронт бывает не только SPA. SSR часто делают на ноде, но ничто не мешает делать его на Kotlin — какая разница на чем шаблоны заполнять? В этом случае логику действительно можно будет шарить между тем же Реактом и SSR-ным бэкендом.

А вообще, новые возможности могут значительно повлиять на применяемые практики программирования. Возможно, наш предыдущий опыт мешает увидеть какие-то новые подходы, которые могут стать преобладающими на рынке.
Таким образом, простое использование Null-safety в Kotlin стимулирует нас применять практику DDD

Сильное заявление...


Возможно для кого-то окажется более приемлемым переход на Dart как на фронтенде, так и на бэкенде вместо Котлина.

Попробовал я как-то дарт для бэка. Не советую – дарт еще менее готов для бэка, чем котлин для фронта.

Волею судеб пришлось мне писать на Котлине и с моей точки зрения он все так-же вбирает в себя все недостатки Java, хотя и пытается их как-то спрятать.


Null safety вещь конечно хорошая, но она не дружит с Java optional и поэтому возникают очень странные ситуации, когда не получается воспользоваться elvis оператором на пустом месте. К тому же есть в этой safety могут быть дыры.
Kotlin multiplatform на бумаге красивая идея, но по факту я вижу в репозитории различные пакеты типа ktor-client-jvm, ktor-client-native, и т.д, что как бы прямо говорит, что никакой мультиплатформенности на самом деле нету, приходится копипастить несколько вариантов кода под разные платформы.
В языке много сахара, но при этом не хватает ортогональности, те же data class это по сути сахар, в более сильных, с точки зрения системы типов, подобные абстракции делаются просто над обычными типами данных без необходимости в новых ключевых словах. В целом, хочу сказать, что ключевых слов в языке реально много, это как раз косвенно свидетельствует о том, что в языке много решений каких-то частных проблем вместо более глубокой проработки самого дизайна языка. Это зачастую приводит к тому, что приходится много частных случаев выучивать.
Еще конечно гигантскую боль приносят Java аннотации и динамическая рефлексия. Это все игнорирует напрочь систему типов, многие проблемы как-то случайно обнаруживаются уже в момент запуска программы, хотя казалось бы, программист предоставил достаточно информации компилятору, чтобы тот проверил соответствие типов во время компиляции.
Хуже того, бывают вообще трудноуловимые проблемы, когда Idea так невзначай тебе подставляет аннотацию с тем же именем, но из другого пакета, в результате они ну не совсем сходятся, но программа даже не падает, она просто в каких-то кейсах имеет неправильное поведение. Это прямо в худших традициях сишного UB. То есть, грубо говоря, вся система аннотаций полностью отменяет все преимущества статической типизации.
Ах да, попробуйте прикрепить десериализацию к sealed data class'ам и порадуйтесь тому, как она прекрасно "работает", а главное отметьте момент, когда вы об этом узнаете.
Опять же, как можно считать язык современным, если в нем можно лишь делать extension методы, но нельзя имплементить интерфейсы.
Еще хочу отметить, что иногда очень удивляют тулы типа ktlint, у которого, когда его используешь через maven плагин, и когда просто в виде команды ktlint --format, отличаются представления о лексикографическом порядке импортов.
Да и в используемых библиотеках часто наблюдал устаревшие туториалы и отсутствие нормальной документации, есть ли вообще в Kotlin экосистеме аналог docs.rs вообще, я ничего подобного не увидел?
То есть язык конечно исправил многие проблемы Java и выглядит как-то получше, но это можно сравнить скорее с косметическим ремонтом, фундаментальные проблемы как были, так и остались, поэтому по настоящему современным ЯП ему не стать.

Я соглашусь, что какие-то проблемы действительно существуют, какие-то были в определенных версиях, но исправлены, с третьими я бы не согласился. Но речь сейчас не об этом. Котлин объективно превосходит Яву в бизнесовой разработке по скорости разработки и по дальнейшей поддержке продукта. Это объективно измеримая величина.
Недостатки у Котлина есть, как и у любого другого сущего элемента в реальном мире. Но, когда стоит вполне бизнесовая постановка, под нее выделен вполне определенный бюджет и стоит вопрос лишь на чем разрабатывать, то вариантов на сегодня все же не так много.
И по моему опыту, Котлин показывает более высокие результаты, по крайней мере в сравнении с Явой.

Нисколько не претендую на истину, если предложите какую-то другую достойную альтернативу, с удовольствием изучу вопрос.
> Это объективно измеримая величина.

Можно узнать, есть ли где то результаты замеров? :)
Сами для себя тестировали. Про публичные исследования не знаю.
Ну просто это как раз и интересно. Расскажите про своё тестирование и результаты. Иначе заявления что «объективно измерили» звучат конечно же воспринимаются читателями как пятничный наброс.
Хотелось бы больше услышать, про
обьективно превосходит
, какая статистика была собрана, по каким именно метрикам превосходит и так далее
Это было не научное исследование :) Для себя проверяли.
Просто один и тот же разраб тупо сделал DTO на Java, потом на Kotlin.
Ну за сколько человек набивает 4 килобайта Явовского кода в сравнении с 300 байтами на Котлине?
Естественно, что когда идет более сложный код, там все хитрее и уже больше играет по времени сама проблема нежели язык.
Просто один и тот же разраб тупо сделал DTO на Java, потом на Kotlin.


Мы же знаем, что у всего есть цена. Нужно учитывать контекст задачи. Вот например можно почитать, во что это выливается когда есть дополнительные требования.
Null safety вещь конечно хорошая, но она не дружит с Java optional и поэтому возникают очень странные ситуации, когда не получается воспользоваться elvis оператором на пустом месте. К тому же есть в этой safety могут быть дыры.

Лично меня больше раздражает, что вместо того, чтобы сделать нормальные сумм-типы (да, я в курсе про sealed classes, это не то) и сделать библиотечный класс Optional<T>, разработчики предпочли закостылить optional на уровне языка, получив:


  1. Неортогональный синтаксис для объявления нуллабельных типов и операций над значениями таких типов.
  2. Невозможность вкладывать нуллабельные типы друг в друга, что плохо сказывается на обобщённом коде.
  3. Разбор нуллабельных значений происходит через через специальные if-ы и smart cast-ы и, т. е. условное переприсваивание типа того же идентификатора вместо привязывания типа к новому идентификатору. Мало того, что тайпчекинг каким-то раком оказывается завязан на анализ потока исполнения, так ещё и сами проверки нужно делать достаточно тупыми, чтобы компилятор понял.
Лично меня больше раздражает, что вместо того, чтобы сделать нормальные сумм-типы

В Arrow скоро обещают на этот счет очень интересные штуки сделать.


сделать библиотечный класс Optional<T>

Можно взять тот же Arrow и получать optional + все остальные плюшки ФП. Котлин все-таки не чисто функциональный язык, поэтому текущее решение мне кажется вполне себе хорошим компромиссом между safety и порогом входа.


Неортогональный синтаксис для объявления нуллабельных типов и операций над значениями таких типов.

А что с этим не так?


Разбор нуллабельных значений происходит через через специальные if-ы и smart cast-ы и, т. е. условное переприсваивание типа того же идентификатора вместо привязывания типа к новому идентификатору.

Или я не понял, или в чем проблема привязать к новому идентификатору?


val nullable: String? = smth()
val nonNullable = nullable ?: return

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

С контрактами вроде поинтереснее становится в этом плане.

Можно взять тот же Arrow и получать optional + все остальные плюшки ФП.

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


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

Так это, наоборот, повышает порог входа. Вместо того, чтобы учить Optional как частный случай сумм-типов/sealed class, новичкам приходится учить специальный синтаксис: для объявления нуллабельных типов, для вызова методов на типе внутри нуллабельного, для разворачивания значений (двух видов) — который используется только для этого и больше ни для чего, и который нельзя использовать для своих типов.


Или я не понял, или в чем проблема привязать к новому идентификатору?

В этом проблемы нет, проблема в том, что тип, приписанный значению, зависит от того, в какой ветке условного оператора он находится. Это и затрудняет чтение кода, и неоправданно усложняет компилятор.

Вместо того, чтобы учить Optional как частный случай сумм-типов/sealed class

Т.е. вы считаете, что ADT и монады понять проще, чем nullable?.doSomething()? Все-таки Optional становится действительно полезен, если он используется в совокупности с остальными приемами ФП. В императивном коде nullable?.doSomething() гораздо проще и понятнее для новичков, чем какой-нибудь optional.map { it.doSomething() }.


Синтаксис с ? уже получил достаточное распространение в других языках – Swift, TypeScript, Dart; в то время как Optional еще не так знаком новичкам. В результате я видел конструкции типа:


val x = optional.getOrElse { null }
x?.doSomething()

тип, приписанный значению, зависит от того, в какой ветке условного оператора он находится. Это и затрудняет чтение кода

Возможно, дело привычки. Мне наоборот кажется проще – не надо вводить новую переменную и держать ее в голове. В IDE smart casting выделяется цветом, на код ревью в GitHub – если нет ? и компилятор пропустил – значит тип приведен, на это можно вообще не обращать внимания. Но если это кажется менее читаемым – можно просто запретить на уровне команды, и вводить доп. переменную.

Т.е. вы считаете, что ADT и монады понять проще, чем nullable?.doSomething()?

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

ADT — да, проще. Именно потому, что это общий механизм

Ну по такому принципу и общую теорию относительности должно быть проще понять, чем законы Ньютона :)

Это не так работает. Гелиоцентрическая модель солнечной системы явно проще в расчетах, чем геоцентрическая система с эпициклами. Хотя результаты они могут дать абсолютно одинаковые.

Kotlin multiplatform на бумаге красивая идея, но по факту я вижу в репозитории различные пакеты типа ktor-client-jvm, ktor-client-native, и т.д, что как бы прямо говорит, что никакой мультиплатформенности на самом деле нету, приходится копипастить несколько вариантов кода под разные платформы.

Но ведь так и должно быть. Общая логика в common, детали реализации в платформозависимые модули. Как минимум это работает так, что можно к примур написать HTTP клиент в common коде и использовать его хоть на андроиде, хоть на нейтив. Условный JavaFX тоже написан с использованием платформозависимых библиотек, разница в том что предоставляемый им API это некое подмножество-пересечение возможностей платформ, а на KMP использование родных возможностей платформ это фича.
Насчет имплементации интерфейсов, мне кажется это больше вопрос развития языка. Уже довольно давно идет обсуждение KEEP-87 про тайп классы. Не могу утверждать, что они когда-нибудь это обязательно реализуют, но внимание со стороны JetBrains к этому вопросу точно есть. Как и со стороны сообщества.

А откуда тогда берется необходимость юзать не common пакет, а самому выбирать с суффиксом -jvm, -native, -js.
Это разве не должно быть в рамках одного пакета условной компиляцией решаться?


Вообще чтобы тайпклассы нормально заработали, они должны быть изначально, иначе если их позже приделать, то все равно их не будет толком ни в стандартной библиотеке ни в сторонних. В итоге ими будут мало пользоваться.

А откуда тогда берется необходимость юзать не common пакет, а самому выбирать с суффиксом -jvm, -native, -js

В Kotlin Multiplatform автору библиотеки всё равно нужно распространять артефакты, предназначенные для конкретных платформ — именно они содержат скомпилированные реализации и используются в рантайме и при компиляции/линковке соответствующего платформенного кода на стороне потребителя. Модули с суффиксами платформ (как ktor-client-jvm) как раз содержат эти платформенные части.


Но для библиотек уже давно можно указывать зависимость не на отдельные платформенные части — это пережитки прошлого, которые для некоторых библиотек ещё сохранялись — а одну зависимость на всю библиотеку в целом, после чего билд-система под капотом разберется, для какой платформы какие артефакты использовать. В недавнем пре-релизе 1.4-M2 проделали ещё немного работы, и зависимости на модули Ktor и kotlinx-библиотек тоже можно будет указывать как одну на всю библиотеку: https://blog.jetbrains.com/kotlin/2020/06/kotlin-1-4-m2-released/#hierarchical-project-structure


Это разве не должно быть в рамках одного пакета условной компиляцией решаться?

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

Сильное заявление...

Тут скорее опыт тех команды, с которыми я работал.
Конечно, при желании можно и на Котлине писать отвратительный код. Один мой коллега был замечен в чрезмерном использовании `Any` вместо нормальных типов. За это был посрамлен и научен жизни.

Если это ответ мне, то дело не в отвратительности кода. Просто nullable types и DDD – ну вообще перпендикулярны друг другу. "Стимулирует писать более чистый код" – ок, но DDD-то тут при чем?

Как он жил вообще с Any? кастовал везде или что?

Все эти виртуальные машины как-то слишком из нулевых. Пора переходить на native code. Чем плох тот же C++ или хотя бы Go?

Есть проект Kotlin Native, который еще не успел развиться до уровня Go, но уже многое может

У виртуальных машин вырисовываются определенные преимущества по сравнению с нативным кодом.
1. динамическая оптимизация кода. В рантайме код докомпилируется под конкретные условия работы. Поэтому по производительности код jvm вполне сравним с c/c++.
2. VM обеспечивают мультиплатформенность. Одна программа на JVM одинаково работает как в Windows, так и в Linux и Mac.
3. В новой версии JVM — GraalVM обеспечивается интеоперабельность с Python и JS из коробки. В Native это делается заметно сложнее.

GraalVM — не новая версия JVM, а проект сбоку от основной JVM

На практике производительность JVM обеспечивается закупкой пары дополнительных серверов, которые дешевле, чем программисты. Но долго ли ещё смогут наращивать производительность железа и снижать цену?

Для этого и придумали микросервисы

Тем что в место описания бизнес логики будешь разбираться с низкоуровневыми конструкциями, вылавливать всякие дескрипторы, отслеживать утечку памяти итд. При том что сама бизнес логика например не самого крупного производственного предприятия настолько сложна что описать ее на платформе с высоким уровнем абстракции, с готовым DDD типа 1С проблематично, не говоря уже о языках общего назначения.

Вы про C++11 что-нибудь слышали? А Go тот вообще со сборщиком мусора. В Java так-то от памяти и потоков тоже на 100% не абстрагировались, насколько я слышал

Откройте например регламентные документы по части ведения кадрового учёта и расчёт заработной платы, сколько уйдет месяцев что бы просто в них разобраться, а сколько потребуется лет если не десятилетий что бы реализовать это все на C++ 11…
А теперь представьте что эти регламенты еще к тому же меняются каждый месяц :)
Ну или вон кейс Dodo Pizza посмотрите, на плюсах это все реализовывать наверно было бы не разумно даже на одиннадцатых

для битья опять и опять предлагается PHP и Python, даже сделав укол не в сравнении с Котлином, а с Java, с учетом, что статья про Котлин.


Конечно с угрозой "низкокачественного" кода, как бы навязывая игру с нулевой суммой.

Не уверен, что полностью правильно понял вашу позицию.
Мой камень в огород PHP, Python, JS был лишь в плане строгой типизации.
Ну не только по типизации прошлись, но и в целом по применимости языков.
В PHP тоже есть null safety:
class Foo
{
    private DateTime $createdAt;
    
    public function __construct(DateTime $createdAt) 
    {
        $this->createdAt = $createdAt;
    }
}

class Bar
{
    private ?DateTime $createdAt = null;
    
    public function __construct(?DateTime $createdAt) 
    {
        $this->createdAt = $createdAt;
    }
}

// NOT NULLABLE
new Foo(new DateTime()); // object with datetime

try {
    new Foo(null);
} catch(TypeError $e) {
    var_dump($e->getMessage()); // TypeError 
}

// NULLABLE
new Bar(new DateTime()); // object with datetime
new Bar(null); // object with null


И как понимаете — тоже на уровне IDE все ловится :) То есть некоторые аргументы ваши не имеют веса, либо не достаточно раскрыты. Крч, претензия — залезли на холиварную территорию, причем не совсем обоснованно.

А так, Котлин — приятный язык, тут соглашусь. Правда пока не совсем понятно, как работать с бекендом без опыта в Java :( Куда не сунься, все или Андроидное или Спринговое, а тк опыта со всем этим хозяйством нет, то погружение в бекенд без Java кажется тем еще удовольствием.

PS: не сочтите хейтерством, мои комментарии больше ориентированы на читателей, нежели на ваш труд.
Честно говоря, когда работал на PHP, ни про какие Null-safety слышно не было. Сейчас тоже попробовал поискать, но с ходу ничего не нашел кроме оператора `??`. Вероятно, это одна из новых фич и это похвально, что PHP дожил до такого уровня. Но, при том, что PHP остается очень востребованным языком, приходится констатировать, что свою долю рынка в последние 10 лет он методично теряет. Тем не менее, не хочу устраивать холивар по этому поводу, знаю много людей и компаний, которые до сих пор процветают на рынке PHP-разработки.

Правда пока не совсем понятно, как работать с бекендом без опыта в Java

Стоит разделять сам язык и экосистему. В язык войти новичку никаких сложностей нет. Понятно, что для высокого уровня требуется набить руку, но это везде так. Что касается экосистемы, то я не вижу как использование какой-то явовской библиотеки может потребовать знания, собственно, языка/синтаксиса Явы.
Взять тот же JUnit/JUnit5. Весь явизм в Котлин сокрыт от разработчика.

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

Я не против конструктивной дискуссии. Уже узнал для себя кое-что интересное и это прекрасно.

Вероятно, это одна из новых фич и это похвально, что PHP дожил до такого уровня.

Тайп хинты, позволяющие или нет null — фича php 5.0 (2004ый год).


А null coalesce, про который вы говорите — это всего лишь сокращение if-оператора или тернарника:


Заголовок спойлера
// === PHP 4.0+ ===

public function getDateOrNew()
{
    if ($this->date === null) {
        $this->date = new DateTime();
    }

    return $this->date;
}

// === PHP 7.0+ ===

public function getDateOrNew(): DateTimeInterface
{
    return $this->date ?? $this->date = new DateTime();
}

// === PHP 7.4+ ===

public function getDateOrNew(): DateTimeInterface
{
    return $this->date ??= new DateTime();
}

В современном мире PHP — это такой же язык как Java или C# (по идее и подходам). В нём на порядки больше возможностей метапрограммирования, но чуть меньше возможностей по контролю типов (нет дженериков и типизации переменных, в остальном всё идентичное).

Тайп хинты, позволяющие или нет null — фича php 5.0 (2004ый год).

В тайп хинтах ничего нового нет. Они сейчас есть и в Питоне и Яваскриптах. Только на практике мало кто их использует. Проверка на null в один оператор тоже древнее изобретение.
Под нал-сейфти сейчас понимается именно защит на уровне компилятора от присвоения нала значению, которое не предназначено принимать нал:
val hereWillBeError: String = null // вот такое не должно пропускаться.
val hereIsOk: String? = null // так нормально
Под нал-сейфти сейчас понимается именно защит на уровне компилятора от присвоения нала значению, которое не предназначено принимать нал:

Ну вы там говорили на тему аргументов. Откуда у вас информация об их слабом использовании — не знаю.


Если же требуется пример защиты при присвоении, то с полями можно так:


class Example {
    public string $hereWillBeError = null; // ошибка
    public ?string $hereIsOk = null; // ок
}
да, язык не компилируемый, понятно что не будет ошибок компиляции, тк ее просто нет… но тесты и простой запуск свалят все — явно и громко, если в not nullable присвоить null, и IDE будет ругаться… все как вы в статье писали
sandbox.onlinephpfunctions.com/code/50feba42363f7a187020c7ac99eb72478929def7

а еще и компайл в неком виде есть в некоторых фреймворках — например сборка DI контейнера не пройдет или очистка кеша

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

UFO just landed and posted this here
Дело в том, что второй вариант является антипаттерном, поэтому в боевом коде он встречается редко. И, если уж сравнивать с Котлином, то в объявлении:
class X {
    var a: String = ""
}

На самом деле уже заложен и сеттер, и геттер. Этот код абсолютно аналогичен следующему:

class X {
    var a: String = ""
        get() = field
        set(value: String) {
            field = value
        }
}

И даже в этом виде он более компактен, чем в Яве, хотя функционально является полным аналогом того, что у вас в первом варианте приведен.
UFO just landed and posted this here
Я не думаю, что существуют целенаправленные исследования, доказывающие что один язык объективно лучше другого. Скорее все останавливается на утверждениях, что у такого-то языка такая-то ниша. Это я про личный опыт. Безусловно, личный.

А паттерны появились не на ровном месте.
UFO just landed and posted this here
Никто не пишет
public int x;
потому что есть спека java beans (и библиотеки, поддерживающих эту спеку, тупо не будут работать с полем), а также куча других случаев, когда работа напрямую через поле неприемлемо.
Внутри класса всё-таки почти никто через геттеры-сеттеры не работают (да и то, бывают ситуации, когда это оправдано).
Так что про «программисты думать не хотят» — это глупость. Такой подход годится для write-once кода. Сегодня норм и прямой доступ по полю, через неделю, когда понадобится подключить загрузку объекта из конфига, придётся писать геттеры-сеттеры. А еще через неделю надо будет завраппить класс и всё — приплыли: прямой доступ к любым полям этого класса становится вообще недопустимым. А еще поле нельзя преобразовать в лямбда-функцию, в отличие от геттера.

А чтобы не писать boilerplate, давным давно существует Lombok, но, как оказалось, подружить в одном проекте Lombok с Kotlin-кодом весьма нетривиально (точнее, через maven сборка настраивается без проблем, но Eclipse с таким хитрым конфигом не очень дружит).

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

Ну вообще-то проблемы были исключительно от того, что я запихнул в одном проекте смешал код на java и kotlin. Всё-таки, более типичный случай когда их разделяют по jar-кам и таких проблем не возникает.
А от использование Lombok в чистом java проекте проблем не наблюдал, да и поддержка со стороны IDE нормальная. А вот поддержка Kotlin-а со стороны Eclipse после java мне показалась слабоватой, конкретных причин не назову, это было года 3 назад. Может уже получше стало, хотя мне кажется, что авторам языка не особо интересно развивать его поддержку в конкурирующем продукте.
UFO just landed and posted this here
Я тоже могу привести пример нескольких библиотек, которые умеют и с полями работать. Но если работа с бинами — вспомогательный функционал библиотеки, то просто берут стандартный java.beans.Introspector, получают набор геттеров-сеттеров и работают через них.

Если говорить про EE, то, например, через CDI просто не будут работать классы с полями вместо геттеров/сеттеров (если только не использовать Singleton scope), т.к. при обращении с injected-бином по сути идёт работа со сгенерированным proxy-классом, тут построить работу с полями в принципе не возможно. Про врапперы я выше уже писал, их тоже не сделаешь нормально.

В целом, если вы пишите код, который будет использоваться в других проектах как библиотека, прямое использование public не-final полей, с которыми требуется взаимодействовать снаружи библиотеки — моветон, т.к. часть паттернов просто несовместима с использованием полей.
Если же говорить про код внутри проекта — рано или поздно где-то придётся заменять поля на геттеры-сеттеры. Только вот попутно это приведёт к необходимости модификации кучи мест, где уже было сделано обращение к полям.

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

Проблема в том, что мы заранее не знаем где выстрелит этот оставшийся 1 процент.
Вы страхуетесь не от того, геттеры и сеттеры семантически ничего не делают относительно public, то есть инкапсуляцию теряете так и так, а равно — плодите высоко-связанный код

DDD упомянули, но геттеры и сеттеры все же обсуждаются как «вариант» написания кода :)

В Котлине data-классы названы как data не просто так. А вот использовать data object кроме как DTO — вопрос уже к программистам, пишущий код и строящим архитектуру.

data class – это не обязательно DTO. Value objects из DDD, да даже просто иммутабельные модели – это все прекрасно реализуется с помощью data class.

Да, но сразу есть одно НО:

  • в дата-классах Котлина есть свойства, через неявные магические сеттеры/геттеры
  • мое понимание ООП (если ограничиться этой парадигмой при обсуждении) видит в этом риск использования этих свойств, знаний и «бесплатного» доступа к этому состоянию/интерфейсу плодить связанный код, который завязан на состоянии тех или иных объектов и который мог бы быть помещен в эти самые VO и иммутабельные сущности


Вы уж извините, я не джавист/котлинист/шарпист, но точно знаю о таких проблемах как минимум в Java, именно война против анемичных моделей идет из языков Java/C#
и абсолютно точно знаю о таких проблемах в больших PHP-проектах и даже имею с ними дело ежедневно

PS: ВЫше отметил, что Котлин крутой. Иммутабельность по дефолту, дата-классы, сахар с лямбдами, добавление методов к классами из др модулей — заставляют слюни пускать, но как и отметил выше — для бекендера без знаний Java (на достойном уровне) тяжеловато на текущий момент браться за Котлин, тк своего родного маловато в нем — могу ошибаться.
И в этом плане Golang для целого ряда программистов из Ruby, PHP, JS кажется отличным вариантом
в дата-классах Котлина есть свойства, через неявные магические сеттеры/геттеры

Если дата-классы иммутабельны (а это, на мой взгляд, хорошая практика), то сеттеров там не будет.


мое понимание ООП (если ограничиться этой парадигмой при обсуждении) видит в этом риск использования этих свойств, знаний и «бесплатного» доступа к этому состоянию/интерфейсу плодить связанный код, который завязан на состоянии тех или иных объектов и который мог бы быть помещен в эти самые VO и иммутабельные сущности

Во-первых, не обязательно ограничиваться ООП, парадигма ФП неплохо реализуется с помощью Котлина.


Во-вторых, не обязательно делать все свойства дата-класса публичными, вполне могут быть private val свойства + публичные методы для реализации логики (и вот уже никакой анемичной модели).


для бекендера без знаний Java (на достойном уровне) тяжеловато на текущий момент браться за Котлин — могу ошибаться

Тут да, полностью согласен. Пока что так. Даже в андроид разработке, где Котлин уже стал "официально рекомендуемым" языком, лучше все-таки знать Java. Вернее даже "знать JVM", многие вещи в учебниках по Котлину просто не описаны, потому что "там все, как в Java".

Во-первых, не обязательно ограничиваться ООП
Ну я не просто так сделал оговорку (не для того, чтобы за нее меня же и мокали) :) Таки да, вы правы
Процедурный подход тоже становится приятным с дата-классами
да уж, я вот в такой ситуации оказался: поигрался с Котлином пару выходных, облизался. А что-то серьезное замутить: интероп с Java, там и спринг и хайбернейт и джексоны/мэксоны… JVM финты для интеропа и вот это все

мне, как нулевому (абсолютли) джависту это показалось сомнительным «плюсом» «нового» языка, который имеет крутые фичи

Ну все-таки справедливости ради проблема наличия фреймворка – это не совсем вина (молодого) языка. Не было бы интеропа с джавой, не было бы и того, что сейчас имеем.


А так, вместо Spring – ktor, вместо Jackson – kotlinx.serialization. Со временем и остальное подтянется. Но пока что, да, нужен опыт Java.


Вообще же, для меня одни из самых крутых фич котлина – это не только null safety и синтаксический сахар, а structured concurrency, контракты, компиляция в native, быстрое развитие языка по сравнению с джавой.


Плюс, я сейчас в основном мобильщик, а не бэкендер, а на Андроиде выбирать между Java 8 и Kotlin – ну тут вообще без вариантов.

Иммутабельность там весьма дырявая, val лишь защищает ссылку от переприсваивания, а вот с самим обьектом по ссылке можно делать что угодно.

Этот код может генерироваться инструментом. Один пример такого инструмента я уже приводил, но на самом деле их намного больше.
Дело в том, что второй вариант является антипаттерном

Это почему он антипаттерн? Если инвариант сохранен — то очень даже паттерн, даже рекомендовано так делать, а если инвариант не сохраняется, то да, так делать неправильно и нужно обязательно делать отдельный метод для установки параметра и связанных с ним вещей.


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

>вместо того, чтобы писать просто:
>public int x;

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

>Это программисты думать не хотят.
Думать за Hibernate? Ну, в каком-то смысле правильно не хотят. Зачем еще раз думать над тем, над чем уже подумали?

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

Ну да. Собственно, я и не хочу сказать, что такой подход хороший — а скорее то, что замена геттера на публичное поле может сломать нечто сбоку, совершенно не то, что ожидали.

А даже если проблем и не возникает — уж как минимум такие инструменты затрудняют иногда понимание поведения системы в целом. Уже на уровне ломбок это вполне заметно.

Мне кажется насчет того что на Котлин писать быстрее это самовнушение.
Ваш код


data class StorableObject(
    var id: String? = null,
    var data: String? = null
) {
    // Место размещения мапера зависит от задачи
    constructor(wm: WorkModel): this(
        id = wm.id.takeIf { it != -1 }?.toString(),
        data = wm.data.takeIf { it.isNotBlank() }
    )

    // Место размещения мапера зависит от задачи
    fun toWorkModel(): WorkModel = WorkModel(
        id = id?.toInt() ?: -1,
        data = data ?: ""
    )
}

легко пишется на Джава в том же самом подходе.


@Data
public class StorageObject {
    private String id;
    private String data;

    public StorageObject(@NonNull WorkModel workModel) {
        id = Optional.ofNullable(workModel.id).filter(value -> value != -1).map(String::valueOf).orElse(null);
        data = Optional.ofNullable(workModel.data).filter(not(String::isBlank)).orElse(null);
    }

    public WorkModel toWorkModel() {
        return new WorkModel(
                Optional.ofNullable(id).map(Integer::valueOf).orElse(-1),
                Optional.ofNullable(data).orElse("")
        );
    }
}

Да, чуть больше символов, но это как по мне вообще мелочь. Тк именно подход как делать один и тот же.


Джаву все вытесняют и вытесняют а вытеснить не могут. Потому что на мой счет она универсальная для разработки бизнес приложений — и кросплатформеная и быстрая и туева куча библиотек и можно писать умно и быстро, а можно взять новичка и дать ему что то лабать и оно даже будет как то работать.

Не просто символов больше, а читаемость хуже
id? против Optional.ofNullable(id)
toInt() против map(Integer::valueOf)
it.isNotBlank() против not(String::isBlank)
Если вам платят за количество кода, то java здесь выигрывает, да)

Вы действительно думаете, что это настолько существенное различие что вы напишите этот код в два раза быстрее и он еще работать будет быстрее?
Когда вы пишите на языке постоянно то вы вообще не обращаете внимание там id?или Optional.ofNullable(id).


Так же на мой вкус вот такой код id?.toInt() ?: -1 больше смахивает на брейнфак в то время как Джаовский делает тоже самое и также в одну строку, но читая его все просто и понятно Optional.ofNullable(id).map(Integer::valueOf).orElse(-1)


И даже два наших мнения показывают что ваши утверждения субъективные.


П.С. и вообще в java я возьму mapstruct и не буду писать весь этот код :)))) В этом и сила java, что всегда можно сделать разными путями.

Причем тут быстрота, тут дело в читаемости, и имхо в kotlin это реализовано лучше. У kotlin тоже есть свои плюшки (включая большинство из джавы): dsl, корутины, мультиплатформенность кода и тд. Конечно, все зависит от программиста, и там, и там можно как запороть, так и искусно написать код
и имхо в kotlin это реализовано лучше.

в том то и дело что ИМХО. Но я изначально говорил что суть написание кода остается такой же как и в Джаве. Те это не какой то сдвиг парадигмы как в Расте. А просто сахарка присыпали причем много можно делать на джаве сторонними библиотека или вообще вкусовщина.


корутины,

Я живу в радость с реактивными библиотеками как Lettuce, Vert.x, AWS SDK v2 и тд
где все либо с коробки Mono/Flux либо оборачивается легко. И зачем они мне?
А взять какой нибудь Хибернейт, то его не прикрутишь к корутинам и не сделаешь в один миг асинхронным и не блокирующим.


мультиплатформенность кода

у нас есть Graal или Vertx


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


П.С. А если вы сходите в Го группы то вам расскажут, что https://github.com/bxcodec/go-clean-arch/blob/master/article/usecase/article_ucase.go#L33-L82 это нормально для Го,
а вот такое на Джаве


public Flux<Article> fillAuthor(@NonNull List<Article> articles) {
    return Flux.fromIterable(articles)
            .flatMap(article -> repository.getById(article.author.id)
                    .map(author -> {
                        article.author = author;
                        return article;
                    })
            );
}

это куча библиотек и не Го стиль :)))


Но при этом Го тоже должен подвинуть Джаву :))))

> Так же на мой вкус вот такой код id?.toInt() ?: -1 больше смахивает на брейнфак в то время как Джаовский делает тоже самое и также в одну строку, но читая его все просто и понятно Optional.ofNullable(id).map(Integer::valueOf).orElse(-1)

Ну это же чистая вкусовщина. На мой взгляд оба варианта вполне норм читабельны и понятны. Да, на kotlin я начал посматривать где-то несколько дней назад, но конструкции вида «id?» или оператор имени Элвиса интуитивно понятны и используются много где.
Я не очень интересуюсь этой темой, знаю лишь в общих чертах, что такое Java. Насколько я знаю, Kotlin работает поверх JVM. В свою очередь чьей собственностью является JVM и не будет ли в определённый момент вопрос переведён в юридическую плоскость? Java — коммерческий продукт. Не сможет ли он ограничить использование виртуальной машины при возникновении конкуренции со стороны JetBrains, тем самым осложнив им жизнь?
Не сможет ли он ограничить использование виртуальной машины при возникновении конкуренции

en.wikipedia.org/wiki/HotSpot

На сегодня JVM от Оракл — это лишь одна из реализаций. И даже ее есть две версии: проприеритарная и OpenJDK с версией GPL.
При том, что Оракл отменил поддержку Java 8, она реально доступна от других поставщиков.

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

По моему опыту, утверждение о том, что без знания Java поход в в Kotlin невозможен, безосновательно.
Проблема Котлина без Ява специфична. Например, чисто котлинисты не знают устройство структур: HashMap, LinkedList, etc. Просто потому, что в Котлине идет Map и MutableMap, которые внутри — LinkedHashMap. Ни разу не видел, чтоб это как-то влияло на качество кода.
Так же котлинисты хуже знают внутренее устройство, примитивы и шаблоны многопоточности. Просто потому, что в Котлине работа с многопоточностью реализована на более высоком уровне.
Я не знаю людей, которые бы с радостью переходили с Котлина на Яву, но и такие прецеденты у меня были. Это требует плотного штудирования литературы и освоения низкоуровневых вещей. Но это возможно. Занимает пару месяцев.

Скажу больше, и многие джависты «хуже знают внутренее устройство, примитивы и шаблоны многопоточности»:-).
Вы путаете ситуацию «хуже знают» с вообще " без знания Java".
Скажем, у меня в BG С++. И я сразу хочу учить Kotlin. В результате, очень быстро все выражается в необходимость изучения Java библиотек и фреймворков и тонких особенностей их использования в Kotlin. При том что в обоих мирах постоянно происходят изменения и следить приходиться за обоими, чтобы быть на уровне. Это дополнительная нагрузка на программиста.
Я все-таки не понимаю почему API некой библиотеки требует знания Java.
У меня был джун, которого взяли из мира Шарпа и он не знал Яву до этого. Парень толковый и ничем не выдавал своего незнания классической Явы. В Котлин зашел быстро и каким-то невежеством не отличался.
В качестве кандидатом мы всегда рассматривали выходцев из всех ООП языков.
UFO just landed and posted this here
Ну, ушки — это не так страшно и не так интересно. Сейчас у любого языка есть какие-то ушки — время изобретений с нуля прошло.
Мне больше интересно:
И даже везде в обучающих материалах (курсы, книги) проводят сравнение этих языков в стиле на Java делали так, а на Kotlin иначе.

Вам это помогало или мешало?
Мне этот вопрос интересен — готовлю курс по Котлину. Буду благодарен, если ответите развернуто.
> готовлю курс по Котлину.

На jetbrains academy есть вроде неплохой курс, может пригодится.
UFO just landed and posted this here
После котлина пыха с её долларами кажется даже вполне ничего. Тяжело представить, что кто-то реально вникает в десятки тысяч строк подобного кода. Очень на любителя ИМХО
Поэтому тот факт, что на Kotlin проект получается раза в 2-3 компактнее, более легкочитаемый, более надежный в плане защиты от NullPointerException

Вы щас серьезно? А можно поинтересоваться — в проектах на Котлине с каким объемом кода вы участвовали?
Потому что я заходил на проект, которому на тот момент было более года, десяток микросервисов, всё на Котлине — ииииии!
— Дурацкий val/var где можно и нельзя. Какого типа эта переменная? Какие классы туда могут попадать?
— Код нечитабелен. Абсолютно. Куча лапши из .apply/.run/.also с сменой нескольких контекстов.
— Объектная ориентация нарушена. Ну зачем нам субклассирование. Мы напишем экстеншн метод и скопипастим его в пару-тройку модулей. Потому что быстренько.
— От NPX при работе с Java-библиотеками не спастись.
— Data class-ы ничем не лучше ломбокнутых джавовских.
— Корутины настолько эзотеричны и зубодробительны в понимании, что ими никто не пользуется. Привет старый добрый Executor, Runnable, Future, concurrency.

То есть Котлин не решает проблемы. Он провоцирует их создавать. На крупном проекте через год-два у вас будет всё засрано копипастом и лапшой, а разборки с техническим долгом будут занимать 80-90 процентов времени лидов/архитектов.

Резюмирую: пока язык не будет понятен тупо при открытии кода в блокноте, а не в IDE, пока он не будет защищать программиста от использования антипаттернов и копипаста — это плохой и сырой язык, пользоваться им невозможно.
А можно поинтересоваться — в проектах на Котлине с каким объемом кода вы участвовали?

Крупный BigData проект.
3 года разработки двух команд в сумме около 20 человек.
несколько десятков микросервисов, ближе к сотне.

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

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

Но после освоения языка, те же var и val покажутся действительно очень логичным и мощным инструментом.
Плохой код можно написать на любом языке

Джава от этого как минимум защищает. Строгость, обязательность в выполнении объектной ориентации и следования объявлениям. Котлин же, такое ощущение, что был сделан чтобы «херак — и в продакшн».

нужно еще разрабатывать качественную архитектуру и соблюдать определенную культуру разработки.

Скажем так. Как архитект — я могу быть уверен, что определив какие-то базовые сущности на Java, никто втихаря не припишет им экстеншн, который поменяет семантику одного модуля в отличие от остальных. Отнаследованный класс при ревью заметить проще.
Класс лежит в строго определенном месте. А экстеншны можно раскидать по дереву исходников и ловить боль. Потому что их пишут не по контексту принадлежности, а бизнесовому.

те же var и val покажутся действительно очень логичным и мощным инструментом

Я не вижу здесь инструмента. Я вижу экономию на тексте в духе «Внни т прнс?».
final ClassName varName = ... 

на мой взгляд прозрачнее и лучше чем
val varName = ... 

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

Не защищает

Я не вижу здесь инструмента. Я вижу экономию на тексте

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


val varName: ClassName = ...

Так-то var для type inference и в Java есть.


Потому что их пишут не по контексту принадлежности, а бизнесовому.

И в этом как раз и может быть польза. Но опять же – не нравится, не используйте, язык дает такую возможность, но не навязывает ее. Зато можно делать мощные штуки типа контекстно-зависимых расширений с автодополнением и проверкой компилятором.


Куча лапши из .apply/.run/.also с сменой нескольких контекстов.

Да, это может стать проблемой. Но опять же – не надо лепить куда попало. Если на код ревью я вижу такую мешанину, и мне непонятно, что происходит, я прошу переписать и избавиться от apply/run/also.

Так-то var для type inference и в Java есть.

Есть. Но «котлин-стайл» — это использовать var/val.

… И в этом как раз и может быть польза. Но опять же – не нравится, не используйте…
… Да, это может стать проблемой. Но опять же – не надо лепить куда попало.

А зачем в языке штуки, которые провоцируют проблемы? Это уже не проблемы организации работы команды и культуры разработки. Это уже показатель плохо спроектированного языка.

val varName: ClassName = ... не противоречит котлин-стайлу, насколько я помню. Что котлин, что джава дают одни и те же варианты:


val x = "Test"
val x: String = "Test"

final var x = "Test"
final String x = "Test"

В чем разница-то?


А зачем в языке штуки, которые провоцируют проблемы? Это уже не проблемы организации работы команды и культуры разработки. Это уже показатель плохо спроектированного языка.

Затем, что если использовать их по уму, то они могут быть очень удобными. Вот так, на мой вкус, выглядит читабельнее:


val x = SomeClass().apply {
    doSomething1()
    doSomething2()
    doSomething3()
}

чем так:


val x = SomeClass()
x.doSomething1()
x.doSomething2()
x.doSomething3()

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

В чем разница-то?

Для строк — ни в чем. Когда у вас с правой стороны стоит фабричный метод, который порождает что-то более сложное — хорошо бы с левой иметь не безликий val, а объявление типа. Код должен быть ясным.

val x = SomeClass().apply {
    doSomething1()
    doSomething2()
    doSomething3()
}


Если вам так нравится чейнить вызовы — давайте уж писать сам класс с поддержкой builder pattern:
val x: SomeClass = SomeClass()
  .doSomething1()
  .doSomething2()
  .doSomething3();


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

Продолжая аналогию: пистолет из которого хорошо забиваются гвозди, но нельзя застрелиться — лучше, чем пистолет из которого можно только застрелиться.
Если вы не киллер, конечно, ЕВПОЧЯ.
хорошо бы с левой иметь не безликий val, а объявление типа. Код должен быть ясным.

Так а в чем проблема его туда добавить? Я не понимаю, почему вы в этом плане противопоставляете котлину джаву. И в том, и в другом языке можно как явно задать тип, так и опустить его, чтобы компилятор сам его вывел.


давайте уж писать сам класс с поддержкой builder pattern

Если вы сами пишете этот класс – не проблема. А если это библиотечный класс?


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

Не совсем верное продолжение. "Чем пистолет, из которого можно только застрелиться" – да, лучше. Но где вы видели пистолет, который откажется выстреливать гвоздь, если вы его поднесли к виску и нажали на спусковой крючок?

Я вот не совсем понимаю, в чем проблема с extension-методами. Это же всего-лишь код, который работает с публичным интерфейсом класса. Семантически аналогично какому-нибудь StringUtil, который есть в каждом втором проекте на Java. Или доброй части кода из Guava, ApacheCommons, да даже в JDK есть Collections, Files, Paths и т.п.
На мой взгляд эта фича только способствует ООП, потому-что позволяет четко разделять основную логику интерфейса от методов, которые объявлены просто чтобы с ним было удобно работать. Ну, или если код на Java таким не страдает или не имеет доступ к изменению интерфейса, то позволяет программисту не искать обертку, в которую надо завернуть целевой класс, чтобы получить желаемый функционал и не писать его в нескольких местах. Что, в общем, в обоих случаях win-win. Работаю с интерфейсом, переиспользую код, не ввожу новые абстракции.

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

Так в том то и дело, что код пишется для людей, и на Котлине он пишется нечитабельно.
Я уже который коммент об этом талдычу.

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

Вообще необязательно. Взяли фабрику, получили от нее некий обобщенный интерфейс, который реализован пятью классами, взяли от инстанса метод, который возвращает абстрактный класс, у которого тоже, например пяток наследников.
Что вы тут собираетесь рефачить?

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

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


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


Касаемо val/var. Еще раз: человек — не компилятор. Если вам необходимы типы локальных переменных, то лучше подумать над следующими вопросами:


  1. Зачем мне это? Не пытаюсь ли я делать ту работу, которую должен делать компилятор или статический анализатор?
    Например, для редактирования кода человек использует IDE. Блокнот/web используется для его чтения.
  2. Достаточно ли мои методы просты? Может, тут надо не типы указывать, а часть логики по приватным методам разбить? Также, привет всяким SOLID, YAGNI.
  3. Корректные ли имена используются? Суть должны быть понятна из имен, а не типов.

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


Ровно по этим же аргументам лучше использовать static import. Там, где имена импортируемых элементов говорящие, конечно (isEmpty(...) импортируем, builer() — нет).

Например, для редактирования кода человек использует IDE. Блокнот/web используется для его чтения.

Да, для анализа pull-реквестов, например. Без типизации переменной понимать что хотел сказать автор кода — более затруднительно.

Корректные ли имена используются? Суть должны быть понятна из имен, а не типов.

Когда мне нужен лев, а не утка — мне пофигу как кого зовут.

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

Чойта вдруг? Bean-методы тоже поди харамны? А конфиг с такими методами? А это уже фабрика.

static import удобнее сугубо тем, что он лаконичнее. Лишь бы схожих именований не было.
Да, для анализа pull-реквестов, например. Без типизации переменной понимать что хотел сказать автор кода — более затруднительно.

С моей т.з. это не так. Код с val/var (а они и в Lombok для Java есть) более лаконичный и reviewer тупо меньше читать должен. Становится меньше визуального мусора, поэтому и проще вычленить логику.

Когда мне нужен лев, а не утка — мне пофигу как кого зовут.

А можно конкретнее?

Чойта вдруг? Bean-методы тоже поди харамны? А конфиг с такими методами? А это уже фабрика.

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

Да, я сам N-ое кол-во лет назад говорил «зачем нужны лямбды в Java, есть же анонимные классы — сразу видны все типы, а пишутся они в IDE быстро».
А можно конкретнее?

Можно. Я не хочу помнить/сомневаться. Я хочу видеть. И я хочу чтобы в случае несовпадения левой-правой части была ошибка компиляции.
А в лямбдах вы тоже типы параметров указываете?

Под примерами я имел ввиду пример, что где помогает.
а при чем тут лямбды? внутри тушек лямбд — естественно указываю, если это не однострочник
Касаемо apply, also, let, with, run действительно интересно получается, вроде как полезно использовать и должно увеличить читаемость!!! но при объединении < 2-3 строк имхо бесполезны и только загромождают код лишнеми 2 строками из скобок, для 4+ помогают действительно
Не вкурил до сих пор разницу между ними, ясное дело либо this, либо it, но вот run vs apply или let vs also?)

public inline fun <T> T.apply(block: T.() -> Unit): T
public inline fun <T> T.also(block: (T) -> Unit): T

public inline fun <T, R> T.run(block: T.() -> R): R
public inline fun <T, R> T.let(block: (T) -> R): R
Резюмирую: пока язык не будет понятен тупо при открытии кода в блокноте, а не в IDE, пока он не будет защищать программиста от использования антипаттернов и копипаста — это плохой и сырой язык, пользоваться им невозможно.

Т.е. любой существующий на сегодняшний день язык – плохой и сырой, пользоваться которым невозможно. "Есть два типа языков программирования — те, которые все ругают, и те, на которых никто не пишет".

Т.е. любой существующий на сегодняшний день язык – плохой и сырой

Нет. Просто потому что далеко не во всех есть лапша со сменой контекстов.
А в Котлине есть. Java, C, C++, Delphi, Python, vanilla JS я спокойно пойму не скачивая проект с гитхаба.
Котлин непрозрачен.
Куча лапши из .apply/.run/.also с сменой нескольких контекстов
А никто не говорит, что так нужно делать, смешивать контексты, делать кучу вложенностей из scope методов, более того, говорят так не делать.

Про экстеншн методы вообще не понял. Если что, то они не имеют доступа к приватным полям, это обычные статик методы. Лучше же конечно иметь StringUtil и ещё миллион Util классов? Нет спасибо, экстеншены одно из самых приятных вещей в Котлине.
— Корутины настолько эзотеричны и зубодробительны в понимании, что ими никто не пользуется. Привет старый добрый Executor, Runnable, Future, concurrency.

Этот абзац прям классическое: «Моя лошадь лучше этого вашего дурацкого автомобиля».
То, что ими никто не пользуется — наглая ложь. И не особо они сложные и, в отличии от «старых добрых ...», позволяют легко писать асинхронный код в синхронном стиле без кучи коллбеков.
А никто не говорит, что так нужно делать, смешивать контексты, делать кучу вложенностей из scope методов, более того, говорят так не делать.

Тем не менее — это котлин-стайл, и так рекомендуется делать вместо последовательного вызова методов.

Лучше же конечно иметь StringUtil и ещё миллион Util классов? Нет спасибо, экстеншены одно из самых приятных вещей в Котлине.

Да, лучше util-классы. Они лежат отдельно, и синтаксически не нарушают объектную ориентацию. Отдельное «спасибо» за отсутствие static в котлине.

И не особо они сложные и, в отличии от «старых добрых ...», позволяют легко писать асинхронный код в синхронном стиле без кучи коллбеков.

Судя по туториалам — сложные, эзотеричные, непонятные. Ну, а исходя из ситуации на проекте — почему-то вместо корутин требовалось использовать экзекуторы.
Судя по туториалам — сложные, эзотеричные, непонятные.

Ну это оценочное суждение. Да, туториалы на сайте с документацией по котлину обычно описывают «как делать», но не описывают «а зачем делать так, если и на экзекьюторах норм».
Применимо к котлину идея structured concurrency описана у Елизарова в блоге. Смысл structured concurrency детально разобран тут.
но не описывают «а зачем делать так, если и на экзекьюторах норм»

Именно. На экзекьюторах + примитивы синхронизации, которые известны из классики многопоточного программирования + конкурентные коллекции. По уши.
«coroutines are lightweight threads».

Ок, зачем мне нити, которые легче чем обычные нити, но с которыми обращаться не как с обычными?
позволяют легко писать асинхронный код в синхронном стиле без кучи коллбеков

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

Многие спорят о том, в чем секрет успеха Java. Да, язык консервативен, да, многословен, часто не безопасен, не слишком выразителен и неконсистентен.
Котлин в свою очередь довольно приятный язык, с очень чёткой маркетинговой нишей синтаксического сахара.
Но я думаю Java будет жить. Потому что писать плохой код можно на любом языке — я видел очень много плохого кода на Java и уверен, что Котлин даёт программисту свободу написать ещё хуже (в чем конечно нет вины языка). Java код скушный, но обычно понятный, ужасы кроются на уровень выше — в кривых абстракция, отсутствии архитектуры, копипасте и т.п. Но понятный код даёт ложное ощущение контроля и возможность средним программистам связать воедино куски, которые они не понимают.
Ни в коем случае не утверждаю, что все Java программисты средние — там немало ярких и интересных личностей. Но массовая популярность и спрос, а главное культура языка делают свое дело. И в копилку плюсов идёт все-же возможность как-то заставить этот хаос работать.
Kotlin на мой взгляд это возможность принести в Java проект немного свежего воздуха. Но если вы почувствовали вкус свободы — не стоит останавливаться, идите дальше — есть Scala, Haskell, Rust и много других интересных языков.
Развивайтесь.

В реальном бизнесе все решают деньги. Если Котлин позволяет делать проект дешевле, то выбирать будут его. Конечно с учетом консерватизма разработчиков.
PHP, например, уже лет 10 планомерно теряет свою долю рынка, но до сих пор жив и даже развивается.

В этом то и дело — Java разработчики в среднем дешевле и их больше, что нивелирует размер кода и количество багов. Тем более что на Java проект можно набрать и хороших и средних выдерживая баланс качество/стоимость.

Java-разрабы дешевле? Я посмотрел на HH — и не вижу, чтобы Java были дешевле Kotlin-only.
Kotlin замечательный язык. Для меня останавливающим фактором является текущая скорость развития JVM — на подходе records и virtual threads — и то будут ли другие языки успевать за этим. Java и JVM развиваются синхронно и в этом, как мне кажется, основное преимущество перед другими JVM-языками.
Я правильно понял, что виртуальные потоки — это аналог корутин?
Да.
Virtual threads are just threads, but creating and blocking them is cheap. They are managed by the Java runtime and, unlike the existing platform threads, are not one-to-one wrappers of OS threads, rather, they are implemented in userspace in the JDK.
UFO just landed and posted this here
Спасибо за разъяснение. Нисколько не сомневаюсь в том, что Скала реально мощный язык и сыграл значительную роль в развитии рынка языков. Лично я сделал выбор после прочтения обзоров сравнения между Котлином и Скалой. Согласно этому обзору они очень близки, но все же Котлин несколько удобнее. Добило меня то, что попадались на глаза проекты, которые перешли со Скалы на Котлин (не помню какие).
UFO just landed and posted this here
Sign up to leave a comment.

Articles