Мир сходит с ума. Говорят, все новые мобильные проекты на Андроиде пишут исключительно на Котлине. В наше время очень опасно не учиться новым технологиям. Вначале твои знания устаревают, ты вылетаешь с работы, живешь у теплотрассы, дерёшься с бомжами за еду и умираешь в безвестности, так и не выучив функционального программирования. Поэтому я отправился на Курсеру изучать курс Kotlin for Java Developers и начал читать книжку (привет, abreslav, yole), поспрашивал друзей сами знаете откуда и вернулся назад с некой пустотой в душе. Помогите Олегу-путешественнику найти смысл в Котлине!
- Бонус: хаброопрос «Как вы используете Kotlin?»
? В Java ты чаще понимаешь по узкому контексту, что происходит. a = b
— запись в поле или локал, a[1] = 2
— запись в массив. В Котлине за любым простым выражением может стоять сколь угодно сложный код из-за всяких умностей вроде перегрузки. Без IDE ничего не поймёшь. А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
? Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях, создавая кучу промежуточных неленивых копий. Стримы в джаве специально введены для различия между ленивой и неленивой коллекцией. Да, есть инспекция в IDE для этого — потому что инспекции призваны исправлять недостатки языков.
? Кстати, об IDE. Насколько хороша поддержка Kotlin в IntelliJ IDEA? Она действительно лучше, чем для Java? Есть большие сомнения. Может быть, кому-то из JB хватит духу проадвокатировать по данному вопросу.
? Котлин форсит использование it
, что приводит к нечитаемому коду. Что-нибудь типа seq.map { it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }
. Что это вообще было? Имена переменным даны не зря! А тут получается монолог вроде «Возьмём это, прикрутим к нему то, потом его закрутим и если оно стало больше того, то наденем сверху шарнир».
? Цепочки вроде ?.let { foo(it); }?.let { bar(it); }
— это вообще ад и должны быть запрещены в декларации о правах человека. И это считается идиоматично, Карл. В отличие от нормального if. Читать такой код невозможно.
? От интеропа с джавой кровь идёт из глаз. А тут всякие JvmStatic и JvmName, и код превращается в цирк с конями.
Например, вот у нас есть такое:
class C {
companion object {
@JvmStatic fun foo() {}
fun bar() {}
}
}
Относительно помеченного аннотацией метода компилятор сгенерит и статический метод во внешнем по отношению к объекту классе, и метод экземпляра в самом объекте. Возможные варианты:
C.foo();
— работаетC.bar();
— синтаксическая ошибка, ибо метод не статическийC.Companion.foo()
; — остается метод экземпляраC.Companion.bar();
— единственный правильный способ
Оправились от красоты решения? Окей, пошли дальше. Теперь вы готовы понять и принять тот факт, что, например, нельзя одновременно объявить два таких метода:
fun List<String>.filterValid(): List<String>
fun List<Int>.filterValid(): List<Int>
Ведь их сигнатуры на уровне JVM совпадают: filterValid(Ljava/util/List;)Ljava/util/List;
Поэтому нужно подпихнуть специальный костыль:
fun List<String>.filterValid(): List<String>
@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>
А как вам такое: в Kotlin нет checked exceptions. А в Java-реальности они есть. Отряд специального назначения «Боевые протезы» имеет честь представить новый самоходный костыль @Throws
:
@Throws(IOException::class)
fun foo() {
throw IOException()
}
Можно долго рассуждать, что «джависты постоянно ноют, что тут всё не как в джаве». Но если вот это красиво, то что тогда ужасно?
В общем, рекомендуется открыть статью Java-to-Kotlin Interop и своими глазами посмотреть, как это выглядит.
? Автоматические геттеры/сеттеры с добавлением английского слова get и первой буквой проперти в большом регистре (видимо, в локали ENGLISH? Ведь регистр букв системно-зависим) — это страшно.
import java.util.Calendar
fun calendarDemo() {
val calendar = Calendar.getInstance()
if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // call getFirstDayOfWeek()
calendar.firstDayOfWeek = Calendar.MONDAY // call setFirstDayOfWeek()
}
if (!calendar.isLenient) { // call isLenient()
calendar.isLenient = true // call setLenient()
}
}
? Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.
Так как этой фичи нет в джаве, поясню. Можно написать любой метод, слева поставить имя «принимающего класса», и всё — он расширен. Давайте расширим MutableList
функцией swap
:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' относится к листу
this[index1] = this[index2]
this[index2] = tmp
}
val lst = mutableListOf(1, 2, 3)
lst.swap(0, 2) // 'this' внтури 'swap()' будет иметь значение 'lst'
Работа экстеншн-методов возможна, даже если автор специально сделал финальный класс, явно показав, что не хочет сторонних расширений. Получается что-то вроде изнасилования с особым цинизмом. И конечно, они ломают совместимость: что будет, если в следующей версии библиотеки автор добавит методы с теми же именами, но с другим возвращаемым типом? Он должен думать обо всех экстеншн-методах, которые любые люди могут добавить в тот же класс?
Кроме того, невозможно сделать оптимизированные реализации экстеншн-методов в конкретных подклассах. Хотя, казалось бы, вот эта фича могла бы произвести вау-эффект.
? Библиотека местами не продумана. Например, reduce.
Вот как выглядит reduce:
listOf(1, 2, 3).reduce { sum, element -> sum + element } == 6
Там есть только форма с identity (fold), но она не всегда применима.
listOf(1, 2, 3).fold(0) { sum, element -> sum + element } == 6
Кстати, почему Хабр подсвечивает эти две строчки по-разному? Аааа, уже неважно.
По сути, fold и reduce делают одно и то же, но fold требует определённого начального значения, а reduce использует в качестве этого начального значения первый элемент списка. Соответственно, форма без identity кидает исключение для пустой коллекции.
Всегда ли такое поведение всем нужно? Почему не вернуть какое-нибудь Optional
и дать пользователю самому решить, что делать в случае пустой коллекции? Да или хоть null вернуть, раз уж это null-friendly язык.
? Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Напоминаю, дата-классы выглядят так:
data class User(val name: String, val age: Int)
val duncan = User("Duncan MacLeod", 426)
val (name, age) = duncan
println("$name, $age years of age") // печатает "JaDuncan MacLeodne, 426 years of age"
Пара выглядит вот так:
val (name, age) = Pair("Java", 23)
println("$name, $age years of age") // тоже печатает "Java, 23 years of age"
А все потому, что внутри:
public data class Pair<out A, out B>(
public val first: A,
public val second: B
)
Совершенно очевидно, что среднестатистический быдлокодер забьёт писать свои классы на второй день использования, и код превратится в кошмарную пародию на лисп. Складываем огуречные жопки с сотнями нефти, пишем фильтры-франкенштейны и в продакшен. Быстро, просто, нечитаемо.
? Очень странный момент — возможность не указывать возвращаемый тип метода (особенно публичного).
Совсем недавно был случай в C++, от которого меня чуть не разорвало от злости. Программа падала в произвольном месте, а я не понимал — почему. Оказалось, в C++ можно не писать return в методе, который согласно сигнатуре должен что-то возвращать. Это не синтаксическая ошибка согласно стандарту, а undefined behavior. Соответсвенно, программа в рантайме падает с произвольной ошибкой. Чудесный язык — в нем есть специальный синтаксис для неработающих методов. С тех пор я очень аккуратно проверяю, что мы обещали вернуть из метода и что отдали на выходе. Эдакая параноидальная привычка.
И вот теперь, в лучшем в мире языке Kotlin мы можем вообще не указывать возвращаемый тип. Это провоцирует людей писать нечленораздельную лапшу, в которой и ничего не понятно. Если метод a
вызывает метод b
, а тот метод c
, а тот содержит в теле выражение when
, в котором в ветках ещё три метода вызываются d
, e
и f
, попробуй пойми тип метода а
!
fun a(check: Int) = b(check)
fun b(check: Int) = c(check)
fun c(check: Int) =
when (check) {
1 -> d()
2 -> e()
else -> f()
}
fun d() = "result 1";
fun e() = "result 2";
fun f() = "result 3";
fun main(args: Array<String>) {
println(::a.returnType)
for (i in 1..3) println(a(i).javaClass.name)
Причём вначале вроде всё было просто и понятно, а в процессе эволюции поменялось, и капец. Меняешь возвращаемый тип метода f
, и у тебя автоматом меняется возвращаемый тип метода а
совсем в другом пакете, и ты не понимаешь, что происходит.
Изначально в нашем примере выхлоп выглядел вот так:
kotlin.String
java.lang.String
java.lang.String
java.lang.String
Но стоит только поменять определения функций на вот такие:
fun d() = "1";
fun e() = 100500;
fun f() = listOf<String>();
И результат тут же изменится на
kotlin.Any
java.lang.String
java.lang.Integer
kotlin.collections.EmptyList
Никакой кристаллизации API. Для публичных методов явная спецификация API должна быть священной коровой, а Kotlin её не требует.
Заключение
Пожалуй, для начала достаточно. Из этой статьи может показаться, что все в Котлине плохо, но это очевидно, не так. Как минимум, зарплата Kotlin-разработчика обычно неплоха :-)
В недавнем докладе на Joker 2018 (есть слайды), Паша (asm0dey) Финкельштейн отмечал, что на бэкенде Kotlin помогает писать более красивый и лаконичный код (но не всегда это получается), на нем получаются более выразительные тесты, с ним работает GraalVM, и всё это с примерами для Spring, Spring Security, Spring Transactions, jOOQ, и т.п.
Стоит ли переходить на Kotlin с Java для мобильных приложений? Неясно. В любом случае, Kotlin интересный. Давайте в нём покопаемся!
Минутка рекламы. Уже на этой неделе, 8-9 декабря 2018, пройдет конференция Mobius. На ней Святослав Щербина из JetBrains расскажет о том, как писать мобильные приложения на Kotlin Muplitplatform. Кроме того, можно будет пересечься со множеством людей, реально использующих Kotlin, и узнать, зачем и как они это делают. Места все еще есть, а вот времени уже почти не осталось, так что, если хотите прийти, сейчас у вас последний шанс. Билеты можно приобрести на официальном сайте.
Комментарии (299)
dzigoro
03.12.2018 14:36+2Олег забыл написать, что на Мобиус будет выступать Святослав Щербина из JetBrains с рассказом о том, как писать мобильные приложения на Kotlin Muplitplatform.
Moxa
03.12.2018 14:43+2и тернарного оператора нет!
adictive_max
03.12.2018 15:23+1А нафига вам тернарный оператор, если if — это expression?
Moxa
03.12.2018 15:45потому что if требует скобок, больше текста, имхо он менее четаем
Borz
03.12.2018 16:12+1Если не надо вычислять первое значение, то можно коротко так:
var t = Value0.takeIf(expression) ?: Value1
костыль, но чем-то похож на тернарный...
OneeL
03.12.2018 16:29+3Это одна из причин, по которым автору статьи и не нравится Котлин — чрезмерное использование «идеоматических» конструкций. В данной ситуации if будет более разумным выбором, на мой взгляд.
Acuna
04.12.2018 02:21+1Я бы даже сказал по-другому: «чрезмерное использование конструкций „костыль, но чем-то похож на...“)))
Zoolander
04.12.2018 00:53+1тернарный оператор в Kotlin записывается вот так
val max = if (a > b) a else b
это возможно потому, что блоки внутри if возвращают значение
balexa
03.12.2018 14:51+8seq.map { it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }
И что вам мешает писать понятнее? Во первых it неявен, и его указывать не надо, во вторых в вашем же примере напишите.
seq.map { s -> foo(s, 1); }.map { f -> bar(f, 2); }.filter { b -> b.getBaz() > 0; }
или если уж c it
seq.map { foo(it, 1); } .map { bar(it, 2); } .filter { it.getBaz() > 0; }
Все остальные жалобы так же на то, что в котлине можно писать нечитаемый код. Называйте переменные нормально.
даже если автор специально сделал финальный класс, явно показав, что не хочет сторонних расширений.
Экстеншен методы — это синтаксический сахар. У них не динамическая диспетчеризация.
видимо, в локали ENGLISH? Ведь регистр букв системно-зависим
Ага. Прямо как в JavaBeans naming convention. Смотрите метод java.beans.Introspector.decapitalize
Тут можно много о чем спорить, но большинство ваших негодований выглядят притянутыми за уши либо звучат как «смотрите какой быдлокод я могу написать, а язык не мешает».khim
03.12.2018 16:38+3Тут можно много о чем спорить, но большинство ваших негодований выглядят притянутыми за уши либо звучат как «смотрите какой быдлокод я могу написать, а язык не мешает».
Проблема в том, что в этом — сама суть Java: язык, в котором на разработчика надевают смирительную рубашку, так что даже человек без опыта, изучивший Java на двухнедельных курсах может что-то писать по данному ему техзаданию.
Kotlin в эту нишу не вписывается от слова «совсем»… с чем разработчикам, долго работавшим на Java, тяжело смириться.
P.S. Собственно если вы посмотрите на историю развития Java — то это постоянная борьба между людьми, которые хотят новые фичи, так как это позволяет писать более читабельный код — и людьми, которые пытаются их не допустить, потому что они также позволяют читать менее читабельный код…balexa
03.12.2018 16:50+1так что даже человек без опыта, изучивший Java на двухнедельных курсах может что-то писать по данному ему техзаданию.
Да ладно вам. Смочь то он сможет, но это не отменяет того факта, что он будет писать кривой код. Ну и непонятно, что помешает ему называть переменные непонятно, что будет мешать ему заводить кучу анонимных классов и т.д.
Ну вот правда. Рассказывать о том, что на джаве тяжело новичку написать лапшекод — это даже не смешно.
Тут ведь большая часть проблем — совсем не проблемы. fold и reduce работают так везде, странно было бы ожидать другого поведения. Капитализация — как в джаве.
Исключения — да, не как в джаве. Но в jvm во первых проверяемых исключений нет, а во вторых — и в джаве уже вроде как признали, что они были ошибкой. UncheckedIOException не от хорошей жизни появился.
Претензиии к интероперабельности — тоже не в кассу.
В итоге то что от критики осталось? Я вижу только «все не как в джаве» и «методы работают как во всех языках, но мне непонятно».olegchir Автор
03.12.2018 16:54+2> в джаве уже вроде как признали, что они были ошибкой
Кто эти люди, которые признали? Рейнхольд? Роуз? Гёц? Мне checked exceptions как раз кажутся очень крутой фичей, всегда их юзал.balexa
03.12.2018 17:08+1В предыдущем разговоре с вами мы как-то уже выясняли, что мир не вращается вокруг вашего опыта.
Сhecked exceptions во первых бесполезны кроме как для информативной точки зрения. Логика обработки эксепшенов в вашем коде обычно никак не должна зависеть от того, объявлен ли в методе throws, поскольку у нас есть такая вещь как RuntimeException, который может внезапно прилететь из сторонней либы. И хорошо написанный код обязан подобные вещи учитывать. А может оттуда и checked exception прилететь легко. Даже без объявления. Потому что это фича языка, а не платформы.
А во вторых, они слабо совместимы с функциональным стилем программирования.0xd34df00d
03.12.2018 17:53+3А во вторых, они слабо совместимы с функциональным стилем программирования.
Почему? В ФП ML-ского толку я очень люблю возвращать ошибки через
Either ErrorsADT ResultType
, что очевидно изоморфноResultType Function(...) throws(ErrorsADT)
. Очень хорошо и типобезопасно получается.
Другие поинты я тоже могу обсудить с позиции ФП и типобезопасности (в частности, например, почему RuntimeException не нужно обрабатывать так же), но только если вы захотите — я не настолько знаю джава-экосистему, чтобы вот так сходу на неё натягивать свой опыт.
balexa
03.12.2018 18:19+1я очень люблю возвращать ошибки через Either
Ну не через эксепшены же.
Потому что в джаве, при ее наличии требований к checked exceptions код лямбд зачастую превращается из
.map(s -> foo(s))
в
.map(s -> try { return foo(s) } catch(Exception e){ throw new RuntimeException(e) })
И да, в джаве нет монады Try. И не будет. Я задавал этот вопрос Хорстманну на какой-то конфе, в ответ получил вопрос что это «not in spirit of java»0xd34df00d
03.12.2018 18:27+2Смотря какие ошибки. Если у меня нарушены инварианты, которые здесь нарушены не должны быть вообще никак (и которые, например, в языке с более мощной системой типов я мог бы выразить в этих самых типах, или же мог бы поймать, если бы несколько дней потратил на ручку с бумажкой, дабы проанализировать алгоритм), то я не сильно парюсь и пишу
error $ "funcName: " <> show arg <> " is invalid"
или вроде того.
Собственно, такой экзепшон в чистом коде (сиречь в какой-то бизнес-логике) вы поймать потом всё равно не сможете, придётся ловить его где-то на очень высоком уровне, где вы сможете, например, перезапустить нужное вычисление с другими параметрами, отправить емейл/смску себе, создать таск в джире с приводящими к нарушению инвариантов параметрами, или что-то такое.
mayorovp
03.12.2018 18:25+2Проблема checked exceptions в том, что в конструкции throws нельзя использовать типы-параметры.
Как следствие, если тип Either ErrorsADT еще можно выразить на Java, то тип Either в рамках вашего изоморфизма уже невыразим.
Собственно, на этом все ФП и заканчивается.
0xd34df00d
03.12.2018 18:32+2Хм.
То есть, я не могу написать функцию, которая бы принимала две функции с произвольными throws-аннотациями, вызывала бы их обе внутри себя и имела бы throws-аннотацию из их объединения?
mayorovp
03.12.2018 19:06+2Да, и это тоже не можете.
0xd34df00d
03.12.2018 19:12+2А, ну тогда по совокупности понятно, почему к checked exceptions негативно относятся. Плохо зделоли, в общем.
khim
03.12.2018 22:06+1Так всё упирается в то, что давно и очень быстро делали. Дженериков-то там, на самом деле, нету.
Всё что есть — сделано через динамическую типизацию и обмазку в компайл-тайм.Akon32
04.12.2018 11:05через динамическую типизацию
Не совсем. Просто компилятор при получении из метода
T get()
проставляет приведенияInteger i = (Integer)get();
к заранее известным типам. Дальше доступ к полям и методам идёт как обычно. Поиска по имени метода при каждом вызове, к счастью, нет.khim
04.12.2018 13:45Проблема в том, что рантайме никакого
T get()
нету. Есть вовсе дажеObject get()
. И он, вообще говоря, может вернуть что угодно. Динамическая типизация в чистом виде.
А приведение типов и выброс исключения — это как раз та «обвязка», о которой я говорил.
Это неплохо работает, на самом деле, TypeScript устроен почти так же. Просто нужно не забывать об ограниченности такого подхода.mayorovp
04.12.2018 14:05Статическая и динамическая типизация — это фичи языка, а не рантайма. И вернуть что угодно он не может: компилятор гарантирует
*
, что этот каст всегда будет успешным (при соблюдении некоторых правил).
*
на самом деле нет. Чертовы массивы все портят. Вот за каким надом их решили сделать ковариантными?khim
04.12.2018 14:27Не гарантирует компилятор нифига. Раздельная компиляция потому что. И ничто вам не мешает изменить код так, чтобы все ваши инварианты порушились.
mayorovp
04.12.2018 14:30+1Ну, если компилировать с одной версией библиотеки, а использовать потом другую — то да, так и будет. Но зачем так делать-то?
PsyHaSTe
04.12.2018 14:44+1Там есть соседняя тема, где всё это уже обсудили в очередной раз, с самыми последними аргументациями.
Akon32
04.12.2018 15:49+2То, что язык позволяет загружать динамические библиотеки, не делает его языком с динамической типизацией. Это динамическая компоновка.
По аналогии, даже в С/С++ можно подсунуть неправильную dll (но в нормальных сборках всё-таки используют корректные dll, а не какие попало), но язык динамическим от этого не станет. Подозреваю, что даже в хаскеле так можно.
anton_t
04.12.2018 12:58+3Проблема checked exceptions в том, что в конструкции throws нельзя использовать типы-параметры.
Вполне можно. Вот такой код прекрасно скомпилируется, по крайней мере в Java 8:
public void <E extends Exception> void func() throws E { }
mayorovp
04.12.2018 13:16+1Хм, почему-то я об этом не знал… И ведь даже вывод типов работает! В таком случае еще не все потеряно.
khim
04.12.2018 13:48Нужно только понимать, что это, в действительности, просто отмена checked exceptions.
В рантайме-то там никакого E не будет, будет простоthrows Exception
(плюс, возможно, автозаворачивание checked exceptions в unchecked).mayorovp
04.12.2018 14:00А какая разница что там в рантайме? Компилятор же гарантирует, что "левым" исключениям просто неоткуда взяться.
khim
04.12.2018 14:29+1Компилятор не может ничего гарантировать про код, которого он не видит.
А .jar, с которым вы собираете ваш проект вовсе не обязан совпадать с тем, что будет реально задеплоено.mayorovp
04.12.2018 14:30+1Но зачем так делать?
balexa
04.12.2018 16:56Что значит зачем? Это делается постоянно, и это вполне нормальное поведение
Затем, что это внезапно стандартное поведение для j2ee библиотек. См. Dependency Scope = provided, и зачем он нужен.
Или затем, что ваша библиотека может зависеть от super-mega-lib версии 42 и собираться с ней, а тот кто вашу либу подключает — от версии 43. И в рантайме вы будете иметь в класспасе именно 43 версию. Или он может зависеть от другой либы, которая использует новую версию super-mega-lib
Это во первых. А во вторых — компилятор ничего не гарантирует, как сказали.olegchir Автор
04.12.2018 18:03> Затем, что это внезапно стандартное поведение для j2ee библиотек.
j2ee — это та технология, от которой в конце концов отказался Oracle, выпилил все её следы из OpenJDK (включая даже такие повсеместные мелочи как java.bind.xml) и отдал на спасение в Eclipse Foundation? =)
отличный пример, чудесныйbalexa
04.12.2018 18:10Да, замечательный пример. Обратите внимание, один из — просто как пример, где часто используются provided зависимости. Живее всех живых. Сервера приложений и контейнеры от этого никуда не делись.
Я вообще уже рекомендовал вам расширять кругозор, не все в мире джавы крутится вокруг того что делает оракл, и не все приложения на бакенде — это уберджары на спрингбуте. Даже если вы лично больше ничего никогда не видели.olegchir Автор
04.12.2018 18:19Ну как никуда не делись, j2ee уже всё — теперь осталась некая надежда на джакарту, и там основной сэллинг поинт это как раз cloud native из коробки, если это выстрелит — то наследие j2ee будет жить
А про спрингбут… Да я видел много всего, но по ходу пришел к выводу, что уберджары на спрингбуте — это самое крутое и удобное :)
И этот подход даже не про джаву, а вообще, про жизнь. Например, я использую GNU/Linux и вижу, насколько круче юзабилити у докерных контейнеров или «все свое тащу с собой» по сравнению с пакетным менеджером. Сейчас у меня есть несколько сайтов про разные вещи, и я везде перешёл к хранению важного софта в self-contained докерах, включая базу данных. Это не просто какой-то непонятно откуда взявшееся утверждение, а моё личное глубокое убеждение о том, как должно выглядеть правильное решение, как я делаю и буду делать у себя.
если я использую какую-то технологию, то жду, что технология поддерживает это убеждение. Если нет, она просто не подходит, надо брать другую.balexa
04.12.2018 18:29Да делайте ради бога. Непонятно, с чего вы решили что ваше убеждение единственно правильное, и что кроме уберджаров на спрингбуте в современном мире не существует. Вы это упорно пропихиваете везде где можно.
И это не самое удобное. Это вам так кажется, не знаю почему. Возможно от недостатка опыта. Возможно потому что вы не рассматриваете ничего за пределами REST-бакенда
В гриде например, в каком-нибудь MR-решении, или в том же апач шторме ваш спрингбут не заработает. Или когда у вас приложение работает с несколькими базами и там сложные бизнес-правила по транзакциям — замучаетесь со спрингбутом. Короче все что выходит за рамки «написать crud приложение». У меня опыт, что спрингбут дает больше проблем, чем решает, как только ситуация становится чуть-чуть нестандартной,.
И вдвойне непонятно, как этот тезис относится к моему предыдущему комментарию. Я писал про то, как в рантайме может возникнуть ситуация, когда jar отличается от того, что был при компиляции.
если я использую какую-то технологию, то жду, что технология поддерживает это убеждение. Если нет, она просто не подходит, надо брать другую.
Очень хорошая позиция. «Если технология и факты противоречат моему манямирку, то это плохая, негодная технология, зделоли плохо». Я теперь понимаю, откуда все тезисы в этой статье.olegchir Автор
05.12.2018 02:10Если вы собрали всё вместе сами и всё своё тащите с собой (то есть, не используете всякие адовые аппликейшен серверы), то вы полностью контролируете, что за джарки используются. Closed world почти что — всё известно на этапе компиляции. Всё что после этого интересно от среды выполнения — конкретный билд OpenJDK, версия glibc и возможно, ядра Linux. (При этом есть возможность отвязаться и от версии OpenJDK, скомпилировав всё в self-contained exe file, но для этого нужен либо GraalVM, либо Excelsior JET). Очень элегантно. Да, продвигаю это везде — мне нравится, вот и продвигаю. Например, в Golang это поведение по умолчанию.
poxvuibr
04.12.2018 14:40+1Компилятор казалось бы гарантирует, но на самом деле для того, чтобы "левые" исключения спокойно появились где угодно достаточно подключить Lombok
anton_t
04.12.2018 14:33+2Никакого заворачивания точно не будет, потому что в рантайме никаких checked exception нет.
По этой же причине не будет и throws Exception — для JVM просто нет такого понятия, все проверки check exceptions совершает компилятор.
PsyHaSTe
04.12.2018 12:23Тут хорошо расписано.
mayorovp
04.12.2018 12:56+1По вашей ссылке нет ничего что бы относилось к ФП. Иммутабельные данные не могут оказаться в «corrupt state» по построению.
PsyHaSTe
04.12.2018 14:46+1Так мы вроде джаву обсуждаем, а не ФП. Про ФП всколь сказано в контексте «А вот в ML», но мы вроде JVM и все к нему относящееся смотрим.
Плюс, эксепшны в любом случае плохо работают с многопотоком. В распространенных языках вставляют всякие известные костыли вроде
To make it easier for developers to write asynchronous code based on Tasks, .NET 4.5 changes the default exception behavior for unobserved exceptions. While unobserved exceptions will still cause the UnobservedTaskException event to be raised (not doing so would be a breaking change), the process will not crash by default. Rather, the exception will end up getting eaten after the event is raised, regardless of whether an event handler observes the exception.
mayorovp
04.12.2018 14:47+1Мы обсуждаем вот это:
А во вторых, они слабо совместимы с функциональным стилем программирования.
PsyHaSTe
04.12.2018 14:52Ну вот hidden state кмк с ФП плохо сочетается. ФП поощряет использование типов, и информация об ошибках должна быть в них описана. А получается, что написано, что функция возвращает T, а на самом деле оказывается, что
Either<T, Either<Err1,Err2,Err3>>
khim
03.12.2018 16:56+1Рассказывать о том, что на джаве тяжело новичку написать лапшекод — это даже не смешно.
Лапшекод, написанный на Java, те не менее, будет работать и его можно будет понять. А вот в JavaScript можно такого понаписать, что в коде вообще ничего ни понять, ни исправить будет нельзя… Kotlin находится где-то посередине…balexa
03.12.2018 17:13+1Лапшекод, написанный на Java, те не менее, будет работать и его можно будет понять
Тут я согласен. Вернее как. Его скорее всего будет проще понять — так точнее наверное. Хотя в связи с последними нововведениями в джаве то уже может быть затруднительно.
Я могу сказать из своего круга окружения — хорошие разработчики попробовав котлин в своей массе на джаву возвращаться не хотят. Для меня это все таки больший показатель качества языка.khim
03.12.2018 17:26+2Про это я ещё раньше написал. Бегать в смирительной рубашке с завязанными на спине рукавами — тяжело. Но как только вы их отпускаете — «недоучки с двумя месяцами курсов» начинают порождать чёрт-знает-что.
И между этими двумя проблемами развитие Java и прыгает… Kotlin же с самого начала сказал: «да, применяя эти фичи бездумно можно написать непонятный код… и это — не наша проблема», что, собственно, и привело автора статьи в уныние…balexa
03.12.2018 19:02+1Вторая проблема в какой-то мере решается введением ревью, кодстайлов и прочих линтов. Понятно что не идеально, но все таки.
Первая проблема не решается. Да, как вы и писали.
dididididi
04.12.2018 09:49А хороший ли разработчик определяются тем, хотят ли они возвращаться с Котлина на Жава или нет?
maxzh83
03.12.2018 18:06+5Проблема в том, что в этом — сама суть Java: язык, в котором на разработчика надевают смирительную рубашку
Да ладно, ничто этому человеку не помешает написать хренову кучу фабрик, адаптеров, визиторов и прочих абстракций на ровном месте. И читать эту писанину потом также тяжело.khim
03.12.2018 22:30+2Есть ощущение, что это дело привычки. У нас был один пришелец из мира Java — он этих «абстракций» на C++ тоже наплодил столько, что мы их уже третий год изничтожаем.
А ему — нормально, когда один класс без методов расширяет другой класс, тоже без методов, который расширяет ещё один (тоже, разумеется без методов).olegchir Автор
03.12.2018 23:24Может это как-то связано с Open/Closed Principle из SOLID? На новую функциональность нужно пилить новый класс, старый класс менять нельзя. В Java понятно, что сам факт создания класса есть новая функциональность, ибо описание класса доступно через рефлекшен. Может, там где-то RTTI, или даже полноценный RTTR?
khim
03.12.2018 23:40+1Там было три уровня наследования классов. И у них была куча вспомогательных классов.
Самое весёлое — что всё это было нужно для поддержки разных платформ и вполне можно было бы обойтись буквально парочкой ifdef'ов и некоторым количеством шаблонов.
Но в Java нет ifdef'ов и шаблонов, а для их замены есть… вот это.igormich88
04.12.2018 13:55В котлине кстати для мультиплатформенной сборки можно использовать expected(объявление без реализации) методы и классы. Некий аналог header файлов из cpp.
maxzh83
04.12.2018 10:25Есть ощущение, что это дело привычки.
Есть ощущение, что это от переизбытка умных книжек и бездумного им следования.
mayorovp
03.12.2018 15:07+5Претензия к reduce/fold непонятна и выглядит надуманной. Javascript, Haskell, C# — никто не возвращает Optional! Это же попросту неудобно в большинстве случаев!
0xd34df00d
03.12.2018 18:01+2Нуу, foldl1 на пустом списке там кидает экзепшон, например, а аналогичный примитив из линзочек вообще решает этот вопрос требованием свёртки только по моноидам, возвращая единицу, если там пусто. Не могу однозначно сказать, удобно это или нет. Помогает писать безопасный код, в принципе.
mayorovp
03.12.2018 18:17+2Так и я про то же самое говорю. В Котлине все так же, только моноидов нет.
adictive_max
03.12.2018 15:12+4Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад и должны быть запрещены в декларации о правах человека. И это считается идиоматично, Карл. В отличие от нормального if. Читать такой код невозможно.
Знаете, «идеоматично» и «рекомендовано» — это как бы сильно не одно и то же.
На примере JS, посмотрите, какой код компилирует Babel или тот-же Kotlin. Он на 100% идеоматичен, но за написание такого кода руками в приличном обществе принято очень больно бить ногами и эти самые руки отрывать.
Очень странный момент — возможность не указывать возвращаемый тип метода
Во-первых, вам никто не запрещает его указывать, это как минимум правило хорошего тона, даже в языках с динамической типизацией.
Во-вторых, если мне память не изменяет, в Котлине тип dynamic поддерживается только для JS, а для jvm — это вывод типов на этапе компиляции. То есть геморрой с отладкой будет, но скомпилироваться в нерабочий код оно не сможет.
Совершенно очевидно, что среднестатистический быдлокодер забьёт писать свои классы на второй день использования, и код превратится в кошмарную пародию на лисп.
Хорошая предъява к языку.sergeperovsky
03.12.2018 16:11+3Это разумная предъява.
Когда-то из языков изгоняли GOTO, чтобы не было ВОЗМОЖНОСТИ писать путанный код.
Язык не только не должен провоцировать писать плохо, он должен провоцировать писать ясно.OneeL
03.12.2018 16:33+3Я подозреваю, что Pair существует, чтобы можно было писать `mapOf(key1 to value1, key2 to value2)` и подобное. Инфиксная функция `to` как раз и создает пару.
Borz
03.12.2018 17:26+1не обязательно для
mapOf
:
typealias MyPair= Pair<Int, String> infix fun Int.to2(that: String): MyPair = MyPair(this, that) fun main() { val m = mapOf(1 to2 "we") val l = 2 to2 "test" }
это удобнее, чем городить data-класс из двух полей и не зависит от конкретной реализации Map.
А ещё это позволяет "городить" именованный перебор key-value коллекций
PS: и да, olegchir забыл упомянуть, что есть не только Pair, но и Triple...
APXEOLOG
03.12.2018 15:13+4От интеропа с джавой кровь идёт из глаз. А тут всякие JvmStatic и JvmName, и код превращается в цирк с конями.
Согласен, но мое личное мнение — не надо писать на котлине для джавы. Нужно писать на котлине для котлина. Тогда и проблем с интеропом не будет
Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Совершенно очевидно, что среднестатистический быдлокодер забьёт писать свои классы на второй день использования, и код превратится в кошмарную пародию на лиспВ некоторых ситуациях Pair вполне себе подходит (когда нужно передать пару значений буквально на одной промежуточной операции). Не городить же отдельный бессмысленный дата-класс под каждый такой случай. А что до быдлокодеров — если вы насильно уберет у них Pair это не означает что они сразу станут писать хорошо (или не сделают этот Pair себе сами)
Не стоит забывать, что возможность сделать что-то, не означает необходимость делать это.
Я сам, когда начинал знакомиться с Котлином, думал насколько же там все непривычно и неудобно сделано по сравнению с Java. Но спустя пару месяцев я привык, а теперь уже обратно возвращаться не хочу
nerumb
03.12.2018 15:35+3В Java ты чаще понимаешь по узкому контексту, что происходит. a = b — запись в поле или локал, a[1] = 2 — запись в массив ..
Да, частично это так. Но с помощью такой записи можно существенно упростить код:
Как самый простой пример:
HashMap<String, Map<String, String>> someMap = new HashMap<>();
//Java
someMap.put("key","value");
//Kotlin
someMap["key"] = "value"
Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях, создавая кучу промежуточных неленивых копий
Возможно только по незнанию и на первых порах. Одинаковый api позволяет делать многие сложные преобразования гораздо проще. И чтобы не плодить промежуточные цепочки (хотя иногда и они нужны) достаточно просто перейти к sequence.
Кстати, об IDE. Насколько хороша поддержка Kotlin в IntelliJ IDEA? Она действительно лучше, чем для Java?
Да, она хуже :) но не сильно. Если даже сравнивать с тем же Go, то поддержка языка лучше сделана. (да и сам релиз Kotlin состоялся только в 2016, пара лет всего прошло)
Котлин форсит использование it, что приводит к нечитаемому коду. Что-нибудь типа seq.map { it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }. Что это вообще было?
Это может вызывать трудности только по началу. It значительно упрощает написание лямбд с одним аргументом. А в тех местах, где есть сложные преобразования всегда можно перейти к именованному аргументу.
Цепочки вроде ?.let { foo(it); }?.let { bar(it); }
Аналогично, сложности только по началу возникают. И Если таких цепочек становится много, то скорее всего что-то делается не так, и скорее всего можно сделать по другому.
От интеропа с джавой кровь идёт из глаз
Отчасти согласен, что местами не очень удобно, но все довольно просто и понятно. Даже тот же @JvmStatic по больше части не нужен, и функция просто выносится на уровень файла.
Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.
Автору и не нужно думать :)
Это лишь способ сделать api удобнее у классов, которые чаще всего используются в проекте. И в любом случае от них гораздо больше пользы чем вреда.
Библиотека местами не продумана. Например, reduce.
Тот пример с reduce/fold, что вы привели, это довольно устоявшаяся конструкция. И как уже было сказано выше, текущая реализация вполне удобна.
Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Как же без пар :) Они весьма полезны. И в коде очень часто возникает необходимость вернуть именно два аргумента, и для этого отлично подходит пара. Да, не везде их нужно использовать, и порой лучше сделать еще один «data class», но для «write once» или просто прототипирования они подходят отлично. А говнокод можно сделать и без них.
Очень странный момент — возможность не указывать возвращаемый тип метода (особенно публичного).
Иногда его и правда можно опустить. Как возвращение того же when, или простые однострочные конструкции. Но по большей части хорошей практикой считается явное указание возвращаемого типа.
vba
03.12.2018 15:39+1так и не выучив функционального программирования
Не совсем понятно причем здесь ФП
vba
03.12.2018 15:46+2Прочитал первый абзац и заключение и понял что автор толком ни в чем не разобрался и наверное не особо хочет разбираться.
Без IDE ничего не поймёшь. А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
Ситуация высосана из пальца. Не соглашусь в корне, в Java тоже есть много всякого во что без IDE сложно въехать. Перегрузку операторов вас никто не заставляет использовать, на Котлине можно писать в Java 6 стиле если очень хочется, дятловать или нет вам решать.
olegchir Автор
03.12.2018 15:50> и наверное не особо хочет разбираться
не просто хочу разбираться, а в некотором смысле это теперь моя работа.
Lexicon
03.12.2018 18:47+3Не совсем понимаю, в чем проблема "Без IDE ничего не поймешь".
Иногда выбор языка основан на степени "ВАУ, СКОЛЬКО ВСЕГО В IDE ДЕЛАЕТСЯ ЗА МЕНЯ!".
Отбрасывать инструмент, с которым проводишь рабочее/личное время потому, что когда-то, возможно, в каком-то поезде, где почему-то нет возможности зарядиться, (но есть необходимость работать) ноутбук сядет на пару часов быстрее — как-то… необычно.vics001
03.12.2018 19:42+1Каждый проект на Github надо открывать в IDE, чтобы прочитать, что там происходит?
siziyman
03.12.2018 21:28+2Большой и на джаве (особенно на современной, куда вот подвезли стримы и автоматический вывод типов локальных переменных) — да, однозначно. Ну, по крайней мере, в той же мере, в какой и на Kotlin.
vsb
04.12.2018 21:50+2Перегрузка операторов, она же не для того, чтобы вам навредить, сделана. Если написано a + b, значит это сложение. Вот и всё. А в детали зачем вам вдаваться? Вы код просматриваете, общую логику пытаетесь понять. В гугле там нашли алгоритм и читаете. Если надо вдаваться в детали, ну можно и влезть, и IDE не обязательно, хотя с ней и проще. Серьёзный анализ кода через просмотр кода в браузере звучит немного странно, хотя, наверное, с умным сервером и такое возможно, просто никто не сделал. А для поверхностного предполагается, что перегрузка операторов используется к месту. К сожалению всегда найдутся уникумы, которые перегрузят какой-нибудь битовый сдвиг для ввода-вывода, но вроде сейчас уже все понимают опасность такого подхода и обычно не злоупотребляют. Я, лично, за несколько лет перегрузил оператор только один раз, это был оператор деления и я его использовал как разделитель каталогов, да и то сомневаюсь в уместности, хотя читается, на мой взгляд, понятно.
balexa
05.12.2018 11:57Да даже если вдаваться.
Я вот лично не понимаю, почему a+b нельзя читать в браузере без IDE потому что непонятно что там происходит внутри оператора сложения, а выражение a.plus(b) — легко, удобно и понятно.
vba
03.12.2018 16:00+2Совершенно очевидно, что среднестатистический быдлокодер забьёт писать свои классы на второй день использования, и код превратится в кошмарную пародию на лисп.
Я писал AWS Lambda на Котлине в ФП стиле с карированием, композицией функций и использованием исключительно функций. Так вот если вы вменяемы и пишите тесты до или после ваших функций, то ваш код выглядит не хуже а даже лучше привычного SOLID-ного кода, ибо тут не один класс, одна ответственность, а одна функция.
Не вижу смысла использовать классы как средство защиты от быдлокодеров, это как минимум предвзято. Шедевры такого рода на несколько порядков чаше встречаются, как раз, в мире ООП откуда и родились анти-паттерны типа God Object итд.
dernasherbrezon
03.12.2018 16:07Мне очень понравились комментарии:
- в Котлине есть вот такая вот неоднозначная фича
- ну так никто не заставляет Вас её использовать.
Интересно зачем вводить фичи в язык, которые не нужно/не надо использовать? Был тут один такой язык С++ называется.
Я покрылся холодным потом, когда читал статью. Если это наше светлое будущее, то похоже пора менять профессию или идти в какие нибудь системщики.
mayorovp
03.12.2018 16:18+1Вы невнимательно читали комментарии, правильная цитата должна выглядеть вот так:
ну так никто не заставляет Вас её так использовать.
Вот есть в языке фича — давать переменным произвольные имена. Хорошая она или плохая? Я считаю что хорошая — я могу дать переменным осмысленные имена, и код станет понятнее. Но кто-то другой даст всем переменным имена a1, a2 и a3 — и код будет непонятным. Становится ли фича "давать переменным произвольные имена" от этого неоднозначной?
dernasherbrezon
03.12.2018 16:25+1Вы передёргиваете. Я не говорил что это плохо или хорошо. Просто есть языки, где важна стабильность и долговременная поддержка. А есть языки где надо по-быстрому. И я и автор ожидали Котлин в первой группе. Оказалось во второй. От этого мы грустим.
olegchir Автор
03.12.2018 16:27+1Ну скажем так… наверное, полезно иметь больше чем одну точку зрения. Чтобы когда условия поменяются, не бегать с горящей задницей кругами :) Точка зрения в посте выбрана очевидно какая, да. Я мог бы написать и с какой-нибудь другой точки зрения, но это неинтересно — обожающих постов, вылизывающих Котлин теплыми щенячьими языками — весь интернет ими переполнен, а толку?
mayorovp
03.12.2018 16:30+1А что не так со стабильностью и долговременной поддержкой? И какое отношение к ней имеют выдуманные претензии из обсуждаемого поста?
Вот три пункта из комментария ниже от Dveim — и те выглядят куда серьезнее...
dernasherbrezon
03.12.2018 16:37Что не так с совершенно обоснованными претензиями из обсуждаемого поста? Всё с ними так. У вас есть пример какого нить долговременного проекта с похожим синтаксисом?
mayorovp
03.12.2018 16:41+1Вы заходите на второй круг. «Совершенно обоснованные» претензии уже разобрали в других комментариях.
Про долговременные проекты на Kotlin ничего не знаю, поскольку пишу на другом языке. Но половина «ужаснейших» с точки зрения автора поста фич давным-давно есть в C#, на котором написан тот же Stack Overflow. C 2008 года и по наше время — это достаточно долгоживущий проект?olegchir Автор
03.12.2018 16:45Ну строго говоря, про C# — это не совсем валидный аргумент. Язык — это больше, чем просто набор фич. Недостаточно просто собрать в кучу все что знаешь, и назвать это языком, с таким подходом это будет скорей помойка :) C# — это целостная система ценностей и трейдоффов, другая система.
rudinandrey
03.12.2018 18:08+1несколько лет назад сбежал из C# разработки в PHP, тогда еще такого ужаса не было, если сейчас оно там все так, то я рад что сделал это еще тогда.
ad1Dima
04.12.2018 08:50Сколько лет назад? Большая часть претензий автора актуальна для c# 3.0 который 10 лет назад релизнулся…
khim
03.12.2018 16:51+2Разумеется. В соответствии с вашими описаниями Kotlin — это п$здец, а C++ — это п$здец? (или даже п$здец?). Тем не менее на C++ имеется куча долговременно поддерживаемых проектов. Гораздо, гораздо больше, чем, скажем, на языке Ада — хотя тот, как бы, специально создавался для «стабильности и долговременной поддержки».
Черезмерное увлечение «стабильностью и долговременной поддержкой» приводит к тому, что хорошие разработчики начинают язык избегать (из-за многословности и невозможности писать код кратко и понятно)… и вот это влияет на проекты, в долгосрочной перспективе, куда сильнее, чем возможность писать быдлокод.dernasherbrezon
03.12.2018 17:11Не знаю как Вы посчитали это влияние. Наверное на листочке где-то. Или влияметром.
Пример крупных проектов на C++ не засчитывается:
1) Он появился давно и альтернатив было немного
2) За 20+ лет появились вот такие вот монстры на основе пота и крови сотни разработчиковkhim
03.12.2018 17:22+11) Он появился давно и альтернатив было немного
Вообще-то тогда (как и сейчас) количество языков исчислялось сотнями. Так что про «отсутствие альтернатив» — не стоит. Вспомните хотя бы Pascal, на котором первые продукты, написанные не на ассемблере, и Apple и Microsoft писали.
2) За 20+ лет появились вот такие вот монстры на основе пота и крови сотни разработчиков
У старых Java проектов — тоже Style Guide'ов хватает, так что мимо.
vba
03.12.2018 17:10+1Мне очень понравились комментарии:
в Котлине есть вот такая вот неоднозначная фича
ну так никто не заставляет Вас её использовать.Секундочку, Котлин язык прагматичный, построенный, между прочим, на опыте использования Java (зачастую на неоправданных ожиданиях), следовательно язык включает в себя некоторые плюшки и даже дуализм в плане парадигмы. Фичи не лежат мертвым грузом, ими очень активно пользуется сообщество.
Но вот если вас похитили злобные
цыганеиндо-россияне и заставляют использовать Котлин а вам вообще в ломы, потому что Java это ваше все, то тогда и только тогда, пишите на Котлине в Java 6 стиле, вам ничего не мешает. Иначе зачем вообще переходить на Котлин?
LMSn
04.12.2018 13:03+1Интересно зачем вводить фичи в язык, которые не нужно/не надо использовать?
Потому что такого никто не говорил. Не нужно передергивать и использовать эти фичи так, как описал автор. Вот с теми же экстеншенами, например, можно на порядок облегчить ряд задач в разработке, улучшить читаемость кода. Нужно просто понимать когда и как эту фичу применять. Написать плохой код можно и без них.
Я пишу на C#, и треть пунктов из поста уже давно и успешно применяются в этом языке. Никто не засоряет код лишними экстеншенами, лишними перегрузками операторов, про ленивое апи коллекций и с чем его кушать могут не знать разве что стажеры/редкие джуны до первого ревью.
aeeneas
03.12.2018 16:08+3Неужели плодить сущности действительно удобнее, чем держать синтаксис минималистичным?
Мне в этом плане куда больше нравится подход Go.argonavtt
03.12.2018 16:28+2go так же плодит новые сущности под каждую задачу, если вы конечно не преобразуете типы через строчку, ну или пустыми интерфейсами грешите много
aeeneas
03.12.2018 16:32+1А можно пример?
snuk182
03.12.2018 18:11+2Любой кейс, архитектурно требующий дженерик. Go предлагает россыпь костылей, плодящих типы.
PsyHaSTe
04.12.2018 12:50Не обязательно. Ведь первый совет… Та даааам
Review the requirements
Step back and revisit the requirements. Review the technical or functional specification (you should have one). Do the specs really demand the use of generics? Consider that while other languages may support a design that is based on type systems, Go’s philosophy is different
…
snuk182
04.12.2018 13:36+1Этот совет не работает на проектах сложности чуть выше микросервиса. Страдают как раз типы, которые надо или плодить, или вынимать рефлексией и тегами.
PsyHaSTe
04.12.2018 14:43+2Ну, автор статьи это вполне серьезно предлагает, на уровне с «Consider copy-paste».
Статья смешная во многих аспектах. Анти-паттерны приводятся как жизнеспособные стратегии для борьбы со сложностью.olegchir Автор
04.12.2018 15:14Ничего не могу поделать — видел как пишут обычные люди в условиях жестких дедлайнов, непонятных требований, недостаточной квалификации, необходимости десятилетиями поддерживать непонятную чертовщину итп — и это знание выгорело на сетчатке глаза, сквозь него теперь весь мир воспринимается по-другому.
AlexeySoshin
05.12.2018 17:11Каждый раз, когда я вижу эту статью, мне становится очень грустно за Go community. А ведь люди же серьезно в это верят. Как и в «ну ничего, скоро уже добавят generic'и».
PsyHaSTe
05.12.2018 17:38Да я глянул слайды с доклада по го2. Такое ощущение, что синтаксис специально сделали таким, чтобы потом тыкать «видите, как неудобно! А мы сразу предупреждали, что это кактус и нинужна».
AlexeySoshin
05.12.2018 17:45Да пусть какой синтаксис будет, мне кажется все лучше, чем кодогенерация из командной строки времен Java5.
mkshma
03.12.2018 16:29+2Из-за минималистичности приходится плодить код. Это очевидный и очень неудобный размен.
aeeneas
03.12.2018 16:32+1Лучше написать пару лишних if-ов, чем плодить неудобочитаемые конструкции из знаков вопроса, восклицания и препинания.
mayorovp
03.12.2018 16:34+1Если бы там и правда появлялась всего-то пара лишних условных операторов — я бы с вами согласился.
aeeneas
03.12.2018 16:41+1Конкретно разворачивания вида
foo?.bar?.x
if-ами и заменяются. Да, больше кода — но зато есть возможность вставить ещё какую-то обработку.mayorovp
03.12.2018 17:18+1Если все что нужно с этим
foo?.bar?.x
сделать — это вернуть, то вы правы. Но в иных случаях там будет еще и две лишних переменных:
var x : X? = null if (foo != null) { val bar = foo.bar if (bar != null) { x = bar.x } }
Причем проблема тут не только в переменных, но и в отсутствии идиоматического способа написания: этот код можно написать 6 разными способами (два способа с ветками else и 1 без них умножить на переменную bar, которая может быть как внутри, так и снаружи), и это только нормальные способы! А ведь есть еще и вот такие:
var x : X? if (foo == null) { x = null } else { x = null val bar = foo.bar if (bar != null) { x = bar.x } }
На этом фоне достоинство
foo?.bar?.x
еще и в том, что такое написание единственно, и к нему можно просто привыкнуть, вместо того чтобы раз за разом разбирать очередной порядок написания условных операторов.
eyeless_watcher
03.12.2018 16:10+2А как вам такое: в Kotlin нет checked exceptions. А в JVM-реальности они есть.
Таки в Java-реальности или в JVM?
Dveim
03.12.2018 16:21+2Ещё немного:
1) котлин хочет усидеть на 3 стульях (js, jvm, native) сразу, и это несовместимо с совместимостью с джавой (put intended). Логика простая: появлятся pure kotlin библиотеки, которые будут частично дублировать функционал уже существующих, но их можно будет использовать в мультиплатформенных проектах. Это приведет к расслоению экосистемы, так как все эти библиотеки будут развиваться независимо друг от друга. Что, в свою очередь, убивает совместимость с джавой; речь не про техническую возможность вызвать код, а про удобство этого процесса.
Пример такого расслоения — scala, где либо есть отдельные java-api, либо библиотеку невозможно использовать из джавы. История повторяется?
Само по себе это разделение не является плохой вещью, но исчезает позиционирование языка как «better java». Что, в случае котлина, является основной selling point для не-андроидоводов.
2) туда же корутины и так называемый kotlin-dsl — библиотека, фундаментально построенная на этом, не будет использоваться из джавы. За примером далеко ходить не надо — ktor пишут лучшие котлинисты мира, но java api там нет.
3) mobile-first развитие => отсутствие плюшек из более новых jvm (8+) в сгенеренном байткоде.ad1Dima
04.12.2018 08:59+1Ничего, по первому пункту есть пример МС с их .Net Standard, которым Jet brains может воспользоваться. Сейчас всё более менее популярные .net библиотеки скомпилированы так, чтоб и можно было использовать на максимальном числе платформ.
PsyHaSTe
04.12.2018 12:53+1Не вижу, Как джава может использовать стандарт. По сути стандарт — это множество C# типов с пустой реализацией, которая компилится в IL. Джава в IL компилиться не будет.
Сделать свой стандарт ничего не мешает, но мне кажется это тоже процесс небыстрый.ad1Dima
04.12.2018 12:56+1Джава компилируется в байткод.
Сделать свой стандарт ничего не мешает, но мне кажется это тоже процесс небыстрый
ну у .Net тоже не сразу получилось. Сначала был PCL.
niklisto
04.12.2018 09:24А разве <kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget> не под 8-ку собирает?
Dveim
04.12.2018 12:46Собирает, но без "мелочей" типа invokedynamic (сейчас анонимные классы, по-старинке) и всего остального, что недоступно на текущей андроид jvm.
Возможная аргументация: тогда будет весьма проблематично распостранять библиотеки. Скажем, написал кто-то утилиту под 11 jvm, с ранее несуществующими фичами, залил jar, и при попытке использовать на старой jvm эти самые ранее недоступные фичи будет ошибка. Даже не при попытке, а при подгрузке байткода.
Это решается (можна снова глянуть на пример скалы), но ценой некоторого удобства пользователя. Ну и таких фич немного. Тем не менее, "android-first" развитие, если в 12 jvm выпустят что-то этакое, то котлин очень нескоро будет генерировать соответствующий байткод.
divanikus
03.12.2018 16:36+1Оказалось, в C++ можно не писать return в методе, который согласно сигнатуре должен что-то возвращать. Это не синтаксическая ошибка согласно стандарту, а undefined behavior. Соответсвенно, программа в рантайме падает с произвольной ошибкой. Чудесный язык — в нем есть специальный синтаксис для неработающих методов.
Я как-то натыкался в коде на такое:
int something; ... if (blabla...) { return x; } else { return something; }
odekolonchik
04.12.2018 13:00+1Извините, за может тупой вопрос. Я пока совсем начинающий. А что в этом коде не так? Или все дело в том, что else можно лишится?
murzilka
04.12.2018 13:30+1Присоединяюсь к вопросу. Тернарный оператор был бы уместнее, но и так, кажется, всё ок
divanikus
04.12.2018 15:02+1Возможно я не очень четко это показал, но там возвращается значение неинициализированной переменной. Т.е. в определенной ветке кода функция возвращает тупо «что-то». Причем программист это понимал, что это будет «что-то» и даже переменную назвал соответствующим образом. Функция, иногда возвращающая рандомный мусор вместо ответа — как тебе такое Илон Маск?
khim
04.12.2018 17:56+1Более весело то, что хороший компилятор может эту ветку из кода выпилить. И безусловно возвращать x.
nemilya
03.12.2018 17:02Прошу извинить за оффтом, но недавно просто читал, и напомнило из книги Л.Л.Васильева «Экспериментальные исследования мысленного внушения»:
другой консультант (проф. М. В. Шулейкин), напротив, скептически относился к самой проблеме и подвергал суровой критике все наши опыты. Этим он принес делу большую пользу.
DEADMC
03.12.2018 17:02+2Если в общем — в котлине можно все писать в джава стиле. Да это ущербно, да так делают только новички, но — можно. Так что ты всегда можешь обмазаться if вместо ?.let.
Теперь по пунктам:
it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }
Ну да, конечно же вместо it, который ни разу не обязателен просто нельзя написать нормальное имя переменной, которое будет понятно читающему такой код, да?
fun List<String>.filterValid(): List<String> fun List<Int>.filterValid(): List<Int>
Можно было просто написать инлайн класс и на него повесить экстеншн. И да, это и нужно в том числе для того, чтобы решить проблему с кучей экстеншнов в коде. И опять же — ну не нравится — не используй. Всегда можно написать top-level функцию и не мучаться.
Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Ну да, делали по сути для двух вещей, для destructuring declarations и для того, чтоб olegchir писал плохой код. В принципе сразу убили двух зайцев одним выстрелом.
Но стоит только поменять определения функций на вот такие:
Явно указать нельзя разве? Или мешает чего?
В общем как итог — вброс конечно же удался, но в целом котлин предоставляет необходимую гибкость нормальным разработчикам и не ставит перед собой цель забить всех в рамки, где шаг влево, шаг вправо —расстрелошибка компилятора. По аналогии можно предъявить тому же Калашникову, за то что обезьяна случайно прострелила себе колено, не разобравшись как это работает. Но этого никто не делает, потому что на обезьян не рассчитано.
vagran
03.12.2018 22:42+1Так что ты всегда можешь обмазаться if вместо ?.let
Не всегда, подобные конструкции приходится применять, когда идёт работа с nullable мутабельным полем, т.к. компилятор считает, что они могли измениться после чтения и стать null, не оставляя никакой возможности сказать ему, что разработчик сам заботиться о синхронизации доступа к таким полям. На мой взгляд это самая спорная фича компилятора. Вот тут пытался обсуждать, если интересно подробнее. В итоге обмазываться приходится всеми этими ?.let.DEADMC
04.12.2018 00:21-1не оставляя никакой возможности сказать ему, что разработчик сам заботиться о синхронизации доступа к таким полям.
О, да ты еще и про котлиновские контракты не слышал! Грустно жить, когда все достижения человечества проходят мимо?
Delfik
03.12.2018 17:16+1Если честно, думаю, что повторю предыдущих комментаторов, но тем не менее…
Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях
Проблема программиста же. Если ты не понимаешь, как что-то работает под капотом, или это работает неожиданно, то имеет смысл винить себя и того, кто ревьювил код (если ревью было).
Котлин форсит использование it, что приводит к нечитаемому коду.
Все нормально читается. Неявная подстановка названия итерируемой переменной вполне удобна. Если уж не нравится, то в требования к коду вводить обязательное использование лямбд с именованным(и) параметром(ами).
Цепочки вроде ?.let { foo(it); }?.let { bar(it); }
Их применение зависит от ситуации. Если вам не важно, в каком месте возник null, то вы напишете так, в противном случае шансы увидеть подобный код после адекватного ревью будут достаточно низкими.
Он должен думать обо всех экстеншн-методах, которые любые люди могут добавить в тот же класс?
Вы перевернули все с ног на голову. Я как автор класса используемой библиотеки о таком задумаюсь с очень малой долей вероятности. Если кто-то экстендит мой класс — его право. Однако, изменение данного класса — моя привилегия и если вы хотите продолжать использовать мою библиотеку, то либо меняйте свои расширения, либо используйте старую версию.
Библиотека местами не продумана. Например, reduce
А вот тут я соглашусь с вами. Мне гораздо удобнее вариант, когда аккумулятор ты создаешь сам. К примеру, как это реализовано в Clojure.
Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Опять же, если код прошел какое-либо ревью, либо не проходил его (если так принято), то это проблема организации бизнес-процессов компании / квалификации ревьюверов и т.д.
p.s. в данном посте обращение «ты» использовано не в панибратских целях, просто изначально свою мысль сформулировал таким образом.
Throwable
03.12.2018 17:16+1Если не вдаваться в детали, описанные автором, то можно согласиться с общим месседжем: код на Kotlin-е становится вцелом менее понятный, чем на Java, особенно при наличии определенного знакомства с языком. При первом знакомстве все пишут как на Java, поэтому все достаточно просто. Но узнав немного язык, разработчик начинает теряться в возможностях, код становится более плотным и лаконичным, но менее структурированным. В частности постоянно возникают вопросы:
- Один класс на файл или несколько классов в файле?
- Как правильно использовать пакеты?
- Когда использовать статические определения вне классов, а когда делать их в компаньонах?
- Использовать экстеншн-методы, или методы класса?
- Делать ли инициализацию полей при объявлении, или выносить отдельно в init{} — блок? И вообще в Котлине нет четкой границы между определением и поведением. При определении поля можно его и проинициализировать объектом, и сконфигурировать при помощи .apply(), и там же навесить хендлеров и листенеров. Структура класса превращается в кашу.
- Нагромоздить однострочник или разбить все по действиям?
- Для операции с объектом переопределить сеттер на свойство или создать отдельный вменяемый метод?
- Общие рекомендации по стилю.
AlexeySoshin
05.12.2018 17:15Ответы на практически все вопросы есть в Kotlin Coding Conventions от авторов языка:
kotlinlang.org/docs/reference/coding-conventions.html
soul_survivor
03.12.2018 18:00+9Автор не понял почему котлин взлетел на андроид — этот язык отвязал программистов от версии jvm, убрал вербозность языка java, встроил самые популярные паттерны программирования в сам язык, добавил возможности dsl которые очень удобно использовать для всевозможных конфигураторов. Хоть котлин и рекламирует 100% совместимость с java но это верно лишь для использования java кода в котлин коде, обратно же никто особо не парится, потому как это дорога в один конец, более высокоразвитый язык поглощает более простой, поэтому не ждите что котлин код будет красиво вызываться из java, совместимость лишь для того чтобы не потерять уже готовый код, но не новый.
vics001
03.12.2018 19:46+1Взлетел — относительное понятие… У Котлина есть преимущества, что вы описали, но код реально превращается в непонятную кашу для ревью и для обычного прочтения. А если тем более не будет совместимости с java / android, то дорога для него пойдет в обратную сторну.
tmteam
04.12.2018 10:26Не будет. Как правильно выше сказали — «наличие возможности именовать переменные как угодно, не доставляет проблем».
Многое из того что есть и удивляет в котлине есть в C# и у нас с этим нет трудностей. Напротив — код очень выразителен и краток. Уверен вам тоже самое скажут и Swift — программисты.
zeolant
04.12.2018 13:04+1этот язык отвязал программистов от версии jvm
Ничего подобного. Внезапно выяснилось, что некоторые конструкции валятся в runtime на 6-ом андроиде, при этом работают на 7 и выше.
Например, мы в команде столкнулись с тем, что map.forEach { (a, b) -> foo() } — матчится в Map.Entry<K, V> (который есть в 6-ой Java), а map.forEach { a, b -> foo() } в BiConsumer<? super K,? super V> (которого нет в 6-ой). При это проект собирается абсолютно без ошибок.soul_survivor
04.12.2018 13:41Это не вина котлина, вы использовали java api которое есть в target sdk, но нет в min sdk, а обход ошибки как раз в использовании котлин апи вместо java api
Wesha
03.12.2018 19:57+2А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
Белые люди в поездах не кодят, они в поездах отсыпаются или, в худшем случае, кину смотрят.
hdfan2
03.12.2018 20:34+3Котлин не читал, но после этой статьи осуждаю. Однако вынужден вступиться за C++. Да, в сферическо-вакуумном C++ отсутствие return это UB, но любой уважающий себя и пользователя компилятор имеет соотв. диагностику, которую можно легко превратить в ошибку. Ну а не использующие такие компиляторы или отключающие варнинги индивиды получают то, что заслужили.
igormich88
04.12.2018 14:13Имхо такую статью можно про любой язык написать. Так советую прежде чем осуждать "почитать".
Guitariz
03.12.2018 20:41+101. Когда я приезжаю на коне к моему бару, мне легко поставить лошадь у поилки. Машину ставить решительно некуда — непродуманная конструкция автомобиля, нужно было делать авто в форме лошади
2. Я мог бросить лошадь посреди поля, и она сама ела бы траву. Машины нужно заправлять дурацким топливом — что за бред, кто вообще на это пойдет
3. Когда я ездил на коне, я носил шпоры. Теперь шпоры мешают мне нажимать на педали. Их идиотская форма не смогла предусмотреть такую очевидную вещь, которой пользуются все наездники
4. Машина требует дороги! На лошади я могу залезть на любой холм, для машины же требуется специально готовить трассу, заниматься освещением, инфраструктурой и остальными вещами. Раньше я мог остановиться посреди поля и заночевать с конем, а теперь привык пользоваться ортопедическим матрасом и 3-звездной гостиницей.
Разница этого рассказа и спора между Kotlin-Java в том, что Kotlin забирает очень мало, но дает очень много нового и не отказывается от поддержки старого. Впрочем в статье жалоба «мне так непривычно, а значит плохо» — прослеживается слишком явно.beeruser
04.12.2018 11:31нужно было делать авто в форме лошади
Так ведь сделали. Мотоцикл называется :)Guitariz
04.12.2018 11:34ага, подковы вешать некуда и еще оно навоз не выделяет. Жалкая пародия на оригинал
Borz
04.12.2018 12:11подковы вешать некуда
чем не подкова? http://drive2moto.ru/uploads/blogs/14525/orig-img1402a.jpg
gorttar
03.12.2018 21:02+5Раз уж автор попросил в Kotlin Community о конструктивной критике по сути, то она у меня есть.
По пунктам с цитатами из статьи:
Добро пожаловать под катВ Котлине за любым простым выражением может стоять сколь угодно сложный код из-за всяких умностей вроде перегрузки. Без IDE ничего не поймёшь. А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
Начать с того, что в чтении/написании Java кода без IDE тоже далеко не уедешь. Что касается самой перегрузки операторов, то в ней особо проблем нет, это всего лишь другой способ объявления методов, который позволяет единообразно писать выражения независимо от типов данных, над которыми эти выражения вычисляются.
Классический пример — BigDecimal в Java:
BigDecimal hundredAndOne = BigDecimal.ONE.add(BigDecimal.TEN.multiply(BigDecimal.TEN));
То же в Kotlin:
val hundredAndOne = BigDecimal.ONE + BigDecimal.TEN ** BigDecimal.TEN
Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях, создавая кучу промежуточных неленивых копий. Стримы в джаве специально введены для различия между ленивой и неленивой коллекцией. Да, есть инспекция в IDE для этого — потому что инспекции призваны исправлять недостатки языков.
В Java, чтобы получить список целых чисел, каждое из которых на 1 больше соответствующего числа из исходного списка нужно будет сделать так:
List<Integer> plusOne = xs.stream().map(x -> x + 1).collect(Collectors.toList());
В Kotlin:
val plusOne = xs.map { it + 1 }
При этом
быдлокодерылюди прекрасно в Java делают collect на каждый чих (на прошлом Joker про это был один из докладов) и никакая IDE инспекция им об этом не говорит.
Котлин форсит использование it, что приводит к нечитаемому коду. Что-нибудь типа seq.map { it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }. Что это вообще было?
Действительно, что это было такое? Если уж использовать it, то не надо стрелочек тогда (рак из точек с запятой в конце каждой строки тоже можно убрать). Надо так:
seq.map { foo(it, 1) }.map { bar(it, 2) }.filter { it.getBaz() > 0 }
Если серьёзно, то в лямбдах (они обычно очень короткие) параметры тоже именуют коротко. Давать длинное имя единственному аргументу, который тут же и используется — много чести. По использованию и так понятно, что в нём, либо же для задачи это не важно.
Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад и должны быть запрещены в декларации о правах человека. И это считается идиоматично, Карл. В отличие от нормального if. Читать такой код невозможно.
Да, с if наверно читаемее:
arg?.let { foo(it) }?.let { bar(it) }
будет равносильно
if (arg != null) { val foo = foo(arg) if (foo != null) { bar(foo) } else null } else null
От интеропа с джавой кровь идёт из глаз. А тут всякие JvmStatic и JvmName, и код превращается в цирк с конями.
Тут соглашусь, использование Kotlin из Java достаточно не удобно просто потому, что очень многих фич Kotlin нет, вот и приходится лишние приседания делать. BTW, обычно Java библиотеки используются из Kotlin, а не в обе стороны. Очень странен проект, где оба языка будут на равных правах присутствовать.
А как вам такое: в Kotlin нет checked exceptions. А в Java-реальности они есть. Отряд специального назначения «Боевые протезы» имеет честь представить новый самоходный костыль @Throws:
Да, в Kotlin выпилили то, что даже в мире Java уже считают антипаттерном (checked exceptions). Для interop сделали аннотацию. В чём проблема?
Автоматические геттеры/сеттеры с добавлением английского слова get и первой буквой проперти в большом регистре (видимо, в локали ENGLISH? Ведь регистр букв системно-зависим) — это страшно.
Чем страшно-то? Геттеры/сеттеры генерируются согласно устоявшимся в Java мире конвенциям.
Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.
Экстеншн-методы не загрязняют никаких интерфейсов. По факту это просто статические хэлпер методы, которые вызываются через точку.
Библиотека местами не продумана. Например, reduce.
reduce так работает во всех языках, где он есть. Я верно понял, что и в Haskell (foldr1, foldl1), и в ruby, и в python они не продуманы, если верить автору?
Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары?
Во-первых для mapOf(...), да и вообще, для тех же целей, для которых в Java есть Map.Entry
Очень странный момент — возможность не указывать возвращаемый тип метода (особенно публичного).
Тип выводится только для методов, которые объявлены как выражение. Если метод абстрактный и тип не указан (в публичном интерфейсе, например), то он Unit по умолчанию. Если нужен не Unit — придётся в интерфейсе явно указать, так что проблема надуманная, как мне кажется. BTW, конвенции по коду в языках с выведением типов (Haskell, Scala, Kotlin и т.д.) требуют указывать их для публичного API
Резюмирую: всё как-то мимо с критикой в статье. Похоже автор плохо разобрался в объекте своей критики.
PsyHaSTe
04.12.2018 13:16+1А я пожалуй соглашусь с последним пунктом, не писать тип функции имеет смысл только на уровне лямбды. Даже в хаскеле емнип не констрейнтить типы функции считается дурным тоном.
khim
04.12.2018 13:55Даже если это внутренняя, приватная, функция?
А так-то да, не писать тип проэкспортированных из библиотеки функций — это даже не дурной тон, за такое морду бить надо.
gorttar
04.12.2018 17:52+1В Хаскеле дурным тоном считается не аннотировать типы публичных деклараций (тех, что экспортируются из модуля). Остальное по желанию.
Да, кстати, в Хаскеле вообще нет различий между лямбдой и именованной функцией, равно как и нет различий между функциональными типами данных и остальными. Более того, так как он тотально ленив, то то, что по типу данных является числом, представляется в памяти как thunk (что-то вроде Supplier в терминах Java). Число вместо него в памяти появляется только тогда, когда понадобилось в других вычислениях.PsyHaSTe
04.12.2018 18:55Да, но если вспомнить, что большинство классов публичные (люди не парятся с internal), то все публичные методы это экспортируемые символы, со всеми вытекающими.
Правда, я не вижу проблем проставлять значения типам всем функциям, чтобы быть уверенным, что в случае ошибки она не пролезла через 10 уровней коллстека откуда-то из кишков.gorttar
04.12.2018 19:01До рантайм ошибки несовместимости типов не пролезут. Если у функции выведен, а не указан явно тип возвращаемого значения (Unit для функций, декларированных как блок; тип выражения для функций, декларированных как выражения), то это не значит, что его нет.
В любом случае, код, использующий функцию, не скомпилируется, если ожидаемый тип возврата не совместим с выведенным по телу функции.
То же верно и для пулбичных val/var деклараций.PsyHaSTe
04.12.2018 19:18Я не про рантайм. Я про то, что если я поменял код, и что-то сломалось, то я точно знаю, что либо поломался код в функции Х, либо в той функции, которая из Х вызывается. Варианта, что вывод поменял типы у десятка функций, в таком случае, нет.
kasthack_phoenix
03.12.2018 21:21+2Вы сейчас описали фичи C#:
вроде перегрузки
Котлин даёт одинаковый API для коллекций и сиквенсов,
в Kotlin нет checked exceptions
Автоматические геттеры/сеттеры
Экстеншн-методы
форма без identity кидает исключение для пустой коллекции.Все они добавлены для того, чтобы не плодить тысячи строк boilerplate-кода. Вы, случайно, не из тех, кто был против
var
, ведьнипанятнаже
?
вроде перегрузки
Напомню, что в Java есть перегрузка + для строк, как special case, да и
[index]
имеет логику внутри, а не просто складывает указатели. Вы используется велосипедный конкатенатор строк, чтобы, не дай бог, не пропустить выделение памяти?
Перегрузка гарантирует унификацию.
.get(...)
/.elementAt(...)
/.charAt(...)
заменяются на[...]
. Наличие обращения к методу вам ничего особо не даёт — в большинстве случаев вы обращаетесь как раз к геттеру / сеттеру, даже если там внутри простое присваивание / чтение поля. В значительной части случаев, реализация скрыта из интерфейсом — знание факта вызова ничего не даёт, в нём может происходить всё, что угодно.
Автоматические геттеры/сеттеры
это страшно
// call getFirstDayOfWeek()
- Чем может помочь знание того, что при обращении к объекту вызывается метод, а не происходит обращение к свойству с неявным вызовом метода?
- Во всех языках с перегрузкой полагают, что любое обращение к стороннему объекту — вызов метода.
- В большинстве случаев, для объектов с поведением, как раз плодятся методы вида
get*
/set*
и унификация доступа к ним снижает количество визуального мусора.x.Width *= 2
вместоx.setWidth(x.getWidth()*2)
.
Ведь регистр букв системно-зависим
Вы слышали о такой вещи, как инвариантная локаль?
@JvmName("filterValidInt")
Да, костыль над type erasure, чтобы иметь возможноть перегружать методы, как в языках с нормальной vm, а не плодить множество разных имён, но при этом иметь читаемый код в Java, а не генерировать суффикс-хэш автоматически.
Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.
Работа экстеншн-методов возможна, даже если автор специально сделал финальный класс, явно показав, что не хочет сторонних расширений
Он должен думать обо всех экстеншн-методах, которые любые люди могут добавить в тот же класс?Это не так работает. Extension-методы — синтаксический сахар, после компиляции они становятся обычными вызовами статических методов, не влияя на целевой тип. Доступа к private / protected членам у них тоже нет.
pnovikov
04.12.2018 08:56Extension-методы — для тех, кто в курсе что такое "behavioral mixin". Я бы сказал, что в этом месте автор критикует то, чего не понимает.
В остальном полностью с вашим комментарием солидарен.
Borz
03.12.2018 21:46+2Без IDE ничего не поймёшь.
без IDE в любом мало-мальски сложном приложении фиг поймёшь
А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир
Jetbrains позаботился о батарейке твоего ноутбука и сделал режим "Power safe mode". Более того — он ещё и настраиваемый.
vics001
03.12.2018 23:53+1Без какого IDE? В очень больших проектах, продвинутый редактор может быть даже полезнее IDE и тормозит меньше.
Borz
04.12.2018 01:30+2Без какого IDE?
Без любого, умеющего делать подсветку/проверку синтаксиса, форматирование кода и запуск на выполнение. Хотя без последнего тоже можно жить, держа рядом консоль.
Arduino IDE вон тоже IDE, хотя по сути это просто "продвинутый редактор" — даже autocomplete нет
tamapw
04.12.2018 09:16Если речь о понимании проекта, то я бы выделил подстветку, функции Go To Definion/Implementation и поиск использований по проекту(семантический, а не тупой перебор), а так же возможность пробежаться глазами по структуре файла. Поиск, разумеется, ленивый должен быть. Это первое, что пришло в голову.
Настроить всё это достойно в «продвинутом редакторе» дело не лёгкое и проблематичное. И всё, что это даст — аналог IDE с чуть большей скоростью в ущерб функционала, поскольку добиться такого же качества едва ли выйдет.
KuVa
03.12.2018 23:25+1Извините меня, но в чём смысл статьи? В том, что на котлине можно написать нечитаемую дичь? Дак это и на джаве можно и на любом другом языке.
А вообще в 2018 не писать андроид приложения на котлин это моветон.
elegorod
04.12.2018 00:54+2Большинство из написанного есть в Groovy, и мне нравится. Половина из этого — плюсы, а не минусы.
yannmar
04.12.2018 03:11-2Это провоцирует людей писать нечленораздельную лапшу, в которой и ничего не понятно.
А не надо чтоб было понятно, надо чтоб было быстро. Все равно код всегда непонятный, если он будет еще чуточку непонятнее, это совершенно ничего не изменит. А вот выкинуть весь чужой непонятный код и быстренько его переписать своим непонятным кодом это кой-чего да стоит.
P.S. А гетеры/сетеры-то чем не понравились, я так и не понял?
kemsky
04.12.2018 04:01Есть интересная статья (и единственная за тоннами хайпа) — Kotlin vs Java The Whole Story. Там сделана попытка обьективно оценить целесообразность перехода на Котлин.
ilitaexperta
04.12.2018 04:49-1Поэтому я отправился на Курсеру изучать курс Kotlin for Java Developers и начал читать книжку
«Разработчикам» с такими скиллами нужно в детсад идти доучиваться, а не стати писать. Нормальный разработчик просто берет и пишет на Котлине, особенно после Джавы, там нечего изучать.
Вообще, выбор между Котлином и Джавой это как выбор между гомосексуализмом и зоофилией. Непонятно что хуже.
У разрабов Котлина стояла задача исправить второй по счету крупный недостаток Джавы — многословность. (Первый — это то что Джава JVM-based, как вы понимаете, он не фиксится). И они с ней вроде справились, но при этом добавили своей.
Итак, по пунктам:
- Сompanion object — че это вообще за концепция такая? Почему я вместо
я вынужден писатьclass C { public final static int CONST = 10; };
class C { companion object{ const val CONST = 10 } }
Какой извращенец это придумал? Какой вообще в этом смысл? Какие преимущества несет?
- Сделали чтобы if возвращал значение, но убрали тернарный оператор. Нахрен мне вообще этот if? Тернарный оператор в разы удобнее.
- var/val. Почему не let\let mut? Нет никакого стимула использовать var. В след версии добавьте van,vam и т.п.
- Функции по умолчанию public. WAT? Т.е вы по умолчанию делаете final class, final function, но при этом я должен писать везде private, где логика?
- lateinit — костыль размером с мамашу автора. Его добавили с целью избавиться от NullPointerException, в результате место него получаем UninitializedPropertyAccessException. Только nullable хотябы проверить на null можно, а lateinit нет. Поменяли стул с пиками пики на другой. Шило на мыло.
- return@listener — это вообще без коментариев.
И таких вещей в Котлине навалом. Без IDE на нем невозможно писать. В С++ наверное синтаксических загонов меньше.
Но если серьезно, то для Андроида выбор между Джавой и Котлином очевиден. В Джаве нет map/filter/reduce(нужно тащить ретростримы), в ней приходится писать жуткий бойлерплет с findViewById и гугл походу окончательно забил на поддержу новых версий.
AChep
04.12.2018 10:481. Не забывайте что можно писать и
const val CONST = 10
class C {
}
5. Можно
if (::myLateinitField.isInitialized) {}
- Сompanion object — че это вообще за концепция такая? Почему я вместо
ufm
04.12.2018 06:54+2Я очень далёк и от Явы и от Котлин, но вот за это:
Работа экстеншн-методов возможна, даже если автор специально сделал финальный класс, явно показав, что не хочет сторонних расширений.
Котлину можно простить всё. И этого очень не хватает в каком нибудь Go. Ибо меня, как программиста, очень мало интересует мнение автора библиотеки.
pnovikov
04.12.2018 08:49Спасибо за быстрое и лаконичное введение в фичи Kotlin-а от лица всех C#-разрабов :)
Как шарпист заявляю: более половины из описанных "странностей" мне очевидны. Kotlin делает так, чтобы писать на Java шарписту стало не отвратительно :) Тут, конечно, есть ещё над чем поработать, но JetBrains движутся в правильном направлении.
Beshere
04.12.2018 08:50У меня такое ощущение, что вокруг Котлина немного искусственно нагнетается шумиха на этом сайте. Нет?
Посмотрите на том же тостере сколько вопросов по Джаве и сколько по Котлину — почти ноль.shanlove
04.12.2018 09:05Tiobe на днях обновился, Котлин по прежнему рядом с хаскелем на задворках. В целом да, ощущение, что шумиха, в основном, в русскоязычном сообществе. И то лишь потому, что котлинисты громкие, а джависты просто сидят и работу работают.
yannmar
04.12.2018 09:27gradle скрипты кстати начиная с пятой версии можно на котлине писать, все примеры на двух языках идут. Так что определенно есть прогресс и не малый
Lamaster
04.12.2018 09:21+1А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
И как часто вы работаете в поезде?
Кстати, об IDE. Насколько хороша поддержка Kotlin в IntelliJ IDEA? Она действительно лучше, чем для Java? Есть большие сомнения.
Ну это аргументация уровня Рен-тв. Совпадение? Не думаю.
Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад
Нормально будет, если в цепочке только один такой оператор. И то от безысходности, если не можешь изменить код библиотечной функции. Рекомендую избегать смешивать nullable и nonNullable типы.
Работа экстеншн-методов возможна, даже если автор специально сделал финальный класс, явно показав, что не хочет сторонних расширений.
Экстеншн методы не про наследование, а про расширение функционала. Финальный класс так и останется финальным. Его никто не наследует и не заберётся в кишки protected.
что будет, если в следующей версии библиотеки автор добавит методы с теми же именами, но с другим возвращаемым типом?
Вам стоит ещё раз почитать документацию. Сработает оригинальный метод.
Да или хоть null вернуть, раз уж это null-friendly язык.
Ага, а потом опять работать с цепочками ?.let { }?.map { }?.filter { }
fun c(check: Int) =
Пожалуйста, не делайте так. Это синтаксис для однострочной лямбда функции.
pnovikov
04.12.2018 09:32По пунктам:
В Котлине за любым простым выражением может стоять сколь угодно сложный код из-за всяких умностей вроде перегрузки.
И? То, что вы не знаете как ведёт себя тот или иной оператор в данном конкретном случае — ваша вина. Язык тут ни при чём. RTFM, наконец!
Без IDE ничего не поймёшь.
А то ж вы в проекте на Java/PHP/C#/C++ много чего без IDE поймёте.
А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
Ну тут мне остаётся только посочувствовать Java-разработчикам (уж не знаю — то ли потому что на поездах ездят, то ли потому что IDE отжирает столько батареи) :)
Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях, создавая кучу промежуточных неленивых копий.
Меня удивляет ваше стремление решать что другие люди должны делать и чего не должны. Вообще-то это неприлично. Вы злоупотребляете? Нет? Ну вот и ладушки. Остальные, полагаю, сами разберутся без вашего мнения.
Котлин форсит использование it
… а вы всё никак не хотите научиться правильно его использовать facepalm
Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад и должны быть запрещены в декларации о правах человека.
Вот опять, обратите внимание. Откуда в вас это стремление запрещать что-либо всем людям сразу, если конкретно вам непонятна эта конструкция? Избавляйтесь от этой черты характера. Некрасиво это.
От интеропа с джавой кровь идёт из глаз.
Это справедливое замечание. Равно и про компаньоны. Равно как и про type erasure.
А как вам такое: в Kotlin нет checked exceptions.
И в C# нет. И в C++ нет. И много где ещё их нет, потому что checked exceptions — это как раз та бессмысленная часть, которая и делает Java многословной без веских на то оснований. В большинстве реальных случаев вам, признайтесь, по барабану список исключений, которые выбрасывает метод. У вас или все ошибки обрабатываются одинаково, или же вы предпочтёте выбрать пару исключений, которые обрабатываются как-то иначе (а все остальные — всё равно одинаково). Тащить за собой список исключений в этой ситуации не имеет никакого смысла. Практической пользы от него — 0.
Автоматические геттеры/сеттеры
Господи, ну наконец-то хоть у одного JVM-языка появилась эта функциональность. Наконец-то на JVM-языке (вкупе с перегрузками) можно писать
point2.Z = (point1.X + point1.Y)*(point2.X - point2.Z)
, а неpoint2.setZ(Point.mult(Point.sum((point1.getX(), point2.getX())),Point.subtr(point2.getX(), point2.getZ())))
. Да здравствует человеческая инфиксная запись! Ура, товарищи! К 2018 году JVM-язык научился делать то, что человечество использует уже несколько веков.
Экстеншн-методы
Читайте что такое примеси и будете вознаграждены. Не надо критиковать то, чего не понимаете — вас на смех поднимут.
reduce и fold
Эм… Вообще-то они так работают во всех языках, в которых они есть. В C# вот fold-а нет. Но его легко можно написать. И когда вы это сделаете — вы поймёте почему fold на пустой коллекции должен бросать исключение.
Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары?
Чтобы вы не захламляли код коммерческого приложения своего работодателя отрядами классов из двух пропертей/полей. Если же вас тянет использовать туплы чтобы заменить ими ВООБЩЕ ВСЁ — вы больны. Не надо проецировать свою болезнь на других.
Очень странный момент — возможность не указывать возвращаемый тип метода
Очередной привет людям, которые пугаются type inference-а. Идите к группе людей вон там в углу, которые не понимают зачем нужен var. И предложите им оперировать хэшмапой тупл-> список generic-интерфейсов без var и с явным указанием результата работы метода. Ах, вы такого в жизни никогда не писали? Так может имеет смысл чуть по-дольше поработать в индустрии прежде чем критиковать?
Для публичных методов явная спецификация API должна быть
Она и есть. Метод возвращает
kotlin.Any
. Это очевидно компилятору, очевидно пользователям kotlin, очевидно пользователям библиотек с такими заковырками. Всем, кроме вас.
В общем, автор. Ничего личного, но поднимайте, пожалуйста, квалификацию.
mayorovp
04.12.2018 10:01-1Она и есть. Метод возвращает kotlin.Any.
Ну уж нет! Метод с автоматически выведенным типом результата возвращает этот самый автоматически выведенный тип, но никак не kotlin.Any.
pnovikov
04.12.2018 10:16Нене. Я подозреваю что там type inferer ищет максимально общего предка для всех возможнных return-значений. Если, как автор в своём примере, мы подпихиваем совершенно несовместимые типы, то в результате разумно предположить что будет
kotlin.Any
. То есть это было сказано про данный конкретный случай, а не вообще. Хотя надо пощупать это поведение.
Иными словами, kotlin не запрещает стрелять себе в ногу. И это правильно.
Reiju
04.12.2018 13:12Ох уж эти проклятые геттеры/сеттеры, спать не дают))
//java int sum = point1.getX() + point1.getY(); int subtr = point2.getX() - point2.getZ(); point2.setZ(sum * subtr); //kotlin var sum = point1.x + point1.y; var subtr = point2.x - point2.z; point2.z = sum * subtr;
valery1707
04.12.2018 14:37+1//Java point2.setZ((point1.getX() + point1.getY()) * (point2.getX() - point2.getZ())); //Kotlin point2.z = (point1.x + point1.y) * (point2.x - point2.z)
Если вам нужны локальные переменные только для упрощения чтения кода — это уже о чём-то да говорит.
Если они нужны где-то ещё далее по коду, то пусть будут конечно. Только в Kotlin рекомендуется использоватьval
.pnovikov
04.12.2018 14:41+1Ребят, обратите внимание что мой пример — он демонстрирует акцессоры вкупе с перегруженными операторами. X/Y/Z в общем случае могут быть не числами.
valery1707
04.12.2018 14:54+1В данном моменте я как раз за Kotlin.
А уж если предположить что поляPoint
не наследникиNumber
, то в Java и математические операторы использовать нельзя.
В случае такого варианта:
//Java (и то только если X/Y/Z иммутабельны) point2.setZ(point1.getX().add(point1.getY()).multiply(point2.getX().subtract(point2.getZ()))); //Java (с переменные для "читаемости") CustomNumber sum = point1.getX().add(point1.getY()); CustomNumber subtr = point2.getX().subtract(point2.getZ()); point2.setZ(sum.multiply(subtr)); //Kotlin point2.z = (point1.x + point1.y) * (point2.x - point2.z)
Что в первом что во втором варианты Java-кода я не могу сходу опередить что там происходит — код приходится парсить глазами.
При этом Kotlin-код читается без проблем.Reiju
04.12.2018 16:25А как себя поведет этот пример в Котлине, если x/y/z «не наследники намбера»? Неужели «объект какого-нибудь класса» + «объект какого-нибудь класса» скомпилируется?
AChep
04.12.2018 10:30-2По содержанию статья похожа на написанную человеком, который до Kotlin видел только Java.
Очень странный момент — возможность не указывать возвращаемый тип метода (особенно публичного).
fun width() = right - left
Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад и должны быть запрещены в декларации о правах человека.
В конкретно этом примере правильнее скорее будет?.let(::foo)?.let(::bar)
.
Хотел проехать по всем пунктам, но не стал. Напишу лучше только что мне самому не нравится в этом языке:
1. Такая форма записи не сработаетval color: Int = 0xFFFFFFFF
.
2. Наличие type-erasure доставшееся от того же JVM.
3. Ограничения с наследованиемdata class
-ов.
3. Конфликты с методами-сеттерами и обычными полями с сеттерами.
open class Foo {
fun setValue(value: int) {}
}
class Bar: Foo() {
var value = 10
}
После Java язык как глоток свежего воздуха.
dididididi
04.12.2018 10:59Каждый раз, когда я читаю про краткость и лаконичность кода, вот честно хочется плакать и менять проффессию. Все программисты представляются хакерами из кино, которые 8 часов в день фигачат код со скоростью 1000 знаков в минуту.
Но если б это было так, то в вакансиях первым делом висело скорость печатания — от 800 знаков в минуту, скорость чтения от 1000 слов в минуту.
Вживую, я ни разу не видел программиста, который соображает быстрей, чем печатает или читает. Никогда, еще на моем веку скорость понимания программы или починки бага не упиралась в скорость чтения или печати.
Большинство программистов, упираясь в лаконичный тернарный оператор, ставят пальчик в экран и морщат в лоб. Многие развертывают лямбды в анонимные классы. Никогда написание и чтение стримов не занимало меньше времени, чем переборка.
Банально с = a>b?a:b — это гораздо хуже, чем с = if(a>b) a else b; хотя тут и букв больше.
Есть определенные пределы сокращения кода, когда он перестает быть читаемым. В идеале скорость чтения должна совпадать со скоростью понимания. Потому что в мозгу вы все равно развертываете тернарную конструкцию в if и на это тратится ценное время.adictive_max
04.12.2018 11:32+4Каждый раз, когда я читаю про краткость и лаконичность кода, вот честно хочется плакать и менять проффессию. Все программисты представляются хакерами из кино, которые 8 часов в день фигачат код со скоростью 1000 знаков в минуту.
Дело не в скорости написания кода. Дело в скорости и простоте его чтения.
Злоупотреблять конечно не стоит, но когда вы не можете написать что-то без boilerplate в 3 раза длиннее полезного кода — это ещё хуже.
Потому что в мозгу вы все равно развертываете тернарную конструкцию в if и на это тратится ценное время.
На своём коде я привык так, что тернарный оператор — по определению простой и односложный, максимум одно сравнение и одна операция. А если встречается if, значит дальше пойдёт какая-то жесть, и надо переключаться с быстрого чтения на вдумчивое.dididididi
04.12.2018 12:29Есть DataObject на java в нем 20 полей, какой правильный способ заполнить их нулями? Длинный конструктор, череда сеттеров, или рефлексия?
adictive_max
04.12.2018 12:42Интересный вопрос, начиная с того, что вы имеете в виду под словом «правильный» и заканчивая кучей неназванных «мелочей», которые могут диаметрально развернуть оценку.
dididididi
04.12.2018 15:53+1вопрос собственно в том что лучше: написать двадцать тупых сеттеров, или рефлексией пробежаться по полям ставя им нолики.
Первый — ненавистный бойлерплейт, но простой как лом и пишется и читается, второй посложней, но увидя код надо напрячь мозги, чтоб понять что он делает, а если с рефлексией давно дел не имел, то скорей всего придется гуглить синтаксис, чтоб написать. Но покороче и потяжелей, но более гибок, не надо дописывать метод, если добавилось новое поле.
Что правильней писать?PsyHaSTe
04.12.2018 16:00+1Третий — с помощью рефлекшна пробежаться по полям и скомпилировать делегат, который будет делать нужную работу с качеством написанного ручного кода. Когда-то делал такую штуку, чтобы сравнить все поля двух объектов в тестах.
Четвертый — сделать нормальное АОП, где просто можно атрибутом повесить
#[derive(Default)]
и получить то же самое от компилятора.
adictive_max
04.12.2018 16:16вопрос собственно в том что лучше: написать двадцать тупых сеттеров, или рефлексией пробежаться по полям ставя им нолики.
Как я уже говорил, зависит от…
А если есть поля, которые надо не трогать?
А если правило заполнения может поменяется?
А если типы у полей разные?
А может ли сигнатура у объекта поменяться и если да, то надо ли новые пполя тоже забивать нулями.
Кроме того, использование 20 тупых сеттеров, — это не бойлерплейт.
Бойлерплейт это объявление 20 тупых сеттеров. То есть у вас тело объявления дата-класса разрастается с 20 строк до 140, если делать всё «по феншую».
ИМХО, в данном случае лучше вообще сделать для объекта метод clear и сохранить значения полей напрямую.
olegchir Автор
04.12.2018 13:02Если вы его создаете через new, они и так заполнены нулями
dididididi
04.12.2018 14:42+1единицами или строками «тут ничего нет»)))
olegchir Автор
04.12.2018 15:18Ну тут ничего не поделаешь, нужно делать memset из единиц с начала раскладки объекта в оперативной памяти и до максимального оффсета :))
PsyHaSTe
04.12.2018 14:55+1Делаете конструктор и заполняете нулями.
По-умолчанию дефолтным состонием объекта можно быть что угодно. Вон, вам "null" например не подходит, хотя именно это дефолтное состояние для типа "ссылка". Вам подавай проинициализированную ссылку.
Учитывая, насколько задача искусственна, нет смысла надеяться на встроенный способ решения.
Akon32
04.12.2018 11:35+1Вы так все фичи записали в баги.
Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.
Всего лишь синтаксический сахар к вызову функции. Позволяет писать более лаконичный, но менее понятный код. Как и автовывод типов. Как и многие другие фичи. Но да, для их использования нужно знать язык, чтобы код был понятным.
Есть, конечно, и сомнительные фичи, но совсем не так много, как вы пишете.
Автоматические геттеры/сеттеры с добавлением английского слова get и первой буквой проперти в большом регистре (видимо, в локали ENGLISH? Ведь регистр букв системно-зависим)
Что значит "системно-зависим"? Не слышал, чтобы .toUpperCase/.toLowerCase работали как-то, отклоняясь от правил юникода.
Без IDE ничего не поймёшь. А IDE плохо, когда ты едешь в поезде и видишь, что свинговый жабоинтерфейс высасывает из ноутбука батарейку как вампир.
Высасывает батарейку не "свинговый жабоинтерфейс", а разбор кода и куча инспекций. Вы же используете эти IDE не из-за любви к странным интерфейсам, а потому, что они помогают решать задачи?
viktprog
04.12.2018 12:14.toUpperCase
/.toLowerCase
в Java зависят от локали, см тут:
Note: This method is locale sensitive, and may produce unexpected results if used for strings that are intended to be interpreted locale independently.
Akon32
04.12.2018 13:17+1И правда. Однако по вашей ссылке есть также совет:
To obtain correct results for locale insensitive strings, use toUpperCase(Locale.ROOT).
Полагаю, разработчики языка учли это.
Cheater
04.12.2018 11:43+3Оказалось, в C++ можно не писать return в методе, который согласно сигнатуре должен что-то возвращать. Это не синтаксическая ошибка согласно стандарту, а undefined behavior. Соответсвенно, программа в рантайме падает с произвольной ошибкой.
Я буду читать предупреждения от компилятора
Я буду читать предупреждения от компилятора
Я буду читать предупреждения от компилятораPsyHaSTe
04.12.2018 13:23+2Это не предупреждение должно быть, а ошибкой.
Настраивать же «варнинги как ошибки» получается нужно очень дотошно, потому что список варнингов многие тысячи, и все их нужно просмотреть, и понять, какие из них на самом деле должны были быть ошибками.
По сути, претензия к тому, что для подобной «фичи» выбран неверный уровень опасности. Это не просто «предупреждение», это полноценный баг в 100% случаев.
— Бтв, интересно, какие ваши действия, когда вы приходите на новое место, а там сборка проекта выплевывает тысяч 10 ворнингов? Переключаете по одному на ошибку раз в неделю на протяжении 25 лет?
PsyHaSTe
04.12.2018 13:37+1Думал некоторое время, отвечать или нет на очевидный вброс, тем более, что выше уже отписались. Решил, что добавлю то, чего выше, как мне показалось, недостаточно расписали.
Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях, создавая кучу промежуточных неленивых копий. Стримы в джаве специально введены для различия между ленивой и неленивой коллекцией. Да, есть инспекция в IDE для этого — потому что инспекции призваны исправлять недостатки языков.
С этим можно согласиться. С другой стороны, тот же C# неявно аллоцирует внутренние коллекции во всяких GroupBy/SortBy, но не помню, чтобы на каком-то проекте это вызывало проблемы. Читаемость и корректность важнее, ради этого собственно итераторы и придуманы.
Котлин форсит использование it, что приводит к нечитаемому коду. Что-нибудь типа seq.map { it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }.
Но вы его почему-то не используете… Кстати, тут косяк котлина, что он не сказал
- что можно написать короче
- что вы используете ключевое слово как имя переменной
Цепочки вроде ?.let { foo(it); }?.let { bar(it); } — это вообще ад и должны быть запрещены в декларации о правах человека. И это считается идиоматично, Карл. В отличие от нормального if. Читать такой код невозможно.
Я может чего-то не знаю, но разве в котлине нельзя создавать промежуточные локальные переменные?
От интеропа с джавой кровь идёт из глаз. А тут всякие JvmStatic и JvmName, и код превращается в цирк с конями.
Не пишите на котлине библиотеки для джавы, вот и все. Пишите на котлине приложение, и те библиотеки, которые будут использоваться только из котлина.
То же относится к вопросу сеттеров (и вообще всего интеропа)
Экстеншн-методы загрязняют публичный интерфейс такими вещами, о которых автор и подумать боялся.
Я правильно понимаю, что если автор библиотеки не предусмотрел метод swap, то нам убиться теперь, потому что нам не разрешено написать свою реализацию? А если нам можно написать свою реализацию, нам прям обязательно её вызывать через
ListHelper.Swap
?
Библиотека местами не продумана. Например, reduce.
Эксепшн вместо Maybe на пустой коллекции это, как мне кажется, косяк. Но в жава-мире вроде вообще принято использовать эксепшны там, где можно было бы обойтись Maybe, так что вроде все справедливо.
Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Вот на это хотел возразить особо.
Вот код, который я написал буквально вчера. Это обработчик телеграм-бота, который пытается понять, была ли в сообщении какая-нибудь картинка, и если есть, пытается получить id картинки в наилучшем разрешении:
let processing_info = match (&update.message.from, &update.message.document, &update.message.photo) { (Some(ref from), Some(ref document), _) => Some((from, &document.file_id)), (Some(ref from), _, Some(ref photo)) => photo .iter() .max_by_key(|x| x.file_size.unwrap_or(0)) .map(|x| (from, &x.file_id)), _ => None, }; let (user, file_id) = match processing_info { Some(x) => x, None => { info!("No values!"); return; } }
Вы правда считаете, что мне стоило вводить на верхнем уровне тип UserWithFileId, просто чтобы вернуть пару значений?
А знаете, что бы вы сделали в java мире? Вы бы объявили пару отдельных переменных, присвоив им null. Потом написали бы кучу ифов, которые при верном значении перезаписывали бы соответствующий null на значение. После всего этого вы бы проверили что из этого null, ну и дальше какие-то действия… В итоге превратилось бы это в простыню раза в 2 длиннее, и ненадежнее из-за перезаписи.
Очень странный момент — возможность не указывать возвращаемый тип метода (особенно публичного).
С этим согласен
Заключение: большинство из этих "багов" — фичи, без которых рождаются
AbstractAnnotationConfigDispatcherServletInitializer
mayorovp
04.12.2018 13:54Ну нет, исключение в reduce — не ошибка. Этот метод просто не предназначается для пустых коллекций, и все. Точка.
Вот, допустим, мне нужно найти произведение шести чисел, и я решил использовать reduce. Мне в ответ вернуло Optional — что мне с ним теперь делать?
Можно, конечно же, дописать какой-нибудь
.orElse(1)
— но подобный код подразумевает, что коллекция из шести чисел может оказаться пустой! А если бы она могла оказаться пустой, я бы использовалfold
вместоreduce
. Поэтому я не будут вызывать.orElse(1)
, я вызову.get()
— и получу для пустой коллекции то самое исключение, от которого меня хотели уберечь.PsyHaSTe
04.12.2018 14:59+1Вот, допустим, мне нужно найти произведение шести чисел, и я решил использовать reduce. Мне в ответ вернуло Optional — что мне с ним теперь делать?
Что угодно. Можно вернуть orElse и получить значение в любом случае. Да, я возможно ожидаю, что элементов может не быть, и хочу предусмотреть это в логике. Писать же логику на try catch плохая идея. В итоге я в любом случае не могу нормально воспользоваться стандартным методом без логики к catch.
А если бы она могла оказаться пустой, я бы использовал fold вместо reduce.
Ну так,
reduce([])
в таком случае вернет None,reduce([0])
— 0. Аfold(0, [])
вернет то же, что иfold(0, [0])
— ноль. А мне может быть важно уметь различать эти случаи.mayorovp
04.12.2018 15:05+1Функция reduce нужна для тех случаев, когда коллекция не может быть пустой. Никакой логики на try/catch писать не требуется. Если нужна какая-то "логика" — нужно использовать fold.
А мне может быть важно уметь различать эти случаи.
Тогда пишите
fold(None, ...)
и настанет вам щастье!PsyHaSTe
04.12.2018 15:25+1Тогда пишите fold(None, ...) и настанет вам щастье!
Тогда в редьюсере аккумулятором будет Maybe, что делает его использование не таким удобным. Сравните
fold(|acc, x| acc + x)
иfold(None, |acc, x| acc.map(|value| value + x))
.mayorovp
04.12.2018 15:41+1Если писать такое однократно и в режиме write-only — не вижу ничего страшного. В конце концов, делал я в однострочниках асинхронный цикл на промисах через reduce и не никто не умер от этого…
Если такое нужно часто — можно и свой хелпер завести.PsyHaSTe
04.12.2018 15:47+1Ну никто не говорит, что это прямо ужас. Просто неудобно.
Наверное, это раст мне мозг перестроил. Я теперь на эксепшны смотрю с ужасом)mayorovp
04.12.2018 15:54+1Вот только что-то я не могу в документации на
std::iter::Iterator
найти аналог fold без указания начального значения аккумулятора...
valery1707
04.12.2018 15:08+1Что-нибудь типа
seq.map { it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }
.
Кстати, тут косяк котлина, что он не сказал
- что можно написать короче
- что вы используете ключевое слово как имя переменной
Это не косяк Kotlin-а — это, в лучшем случае, отсутствие варнинга со стороны IDE.
it
это неключевое слово
, адефолтное имя для переменной, попадающей в ламбду, если это имя явно не переопределить самому
. И никто не запрещает использоватьit
где-либо ещё (что как раз является одним из ограничений ключевых слов) и, уж тем более, никто не запрещает использовать его в качестве имени переменной указанной руками.
Но инспекцию на такое поведение со стороны IDE было бы не плохо иметь.
YetAnotherSlava
04.12.2018 14:31+1>Боевые протезы
Приятно видеть, что кто-то ещё помнит творчество группы «Беломорс».
master1312
04.12.2018 14:49+1в C++ можно не писать return в методе, который согласно сигнатуре должен что-то возвращать
Меж тем у компилятора (gcc по крайней мере) есть ключик -Werror=return-type, превращающий соответствующий warning в полноценный error. Очень рекомендую.
dplsoft
04.12.2018 15:13Апплодирую стоя! на 100% согласен с автором если не в деталях, то в сути поднимаемой им проблемы.
Поиск очередной серебряннойтехнологиипули, помноженный на современные технологии пиара и очковтирательства в купе с увеличением «поголовья хомячков в IT» приводят к действительно безумной ситуации.
И это проблема, т.к. ситуация приносит выгоду только желающим «погреть лапу» на семинарах/пиаре/продажеКнижОнок.
Появление новых фреймворков и языков практически не имеет никакого отношения к решению технических проблем, которые стоят перед разработчиками.
... а проблемы как валялись так и валяются.Стада хомячков гоняются от одного «синтакс-сахорочка» к другому; новые языки придумываются потому, что кому-то не нравится _синтаксис_, а за основу берутся «примитивы» для обучения дошкольников программированию; потом эти «go-go»-новорожденыши пихается в продуктив и «буквальновсе» пиарятся что вот уже «целый гугл» использует «go-go» (ну и конечно же как самый основной язык для самого центрального сервера, а не на задворках для автоматизации скриптов подготовки данных для юнит-тестов); толпы неофитов бьтся в экстазе от того, что на эксклюзивных курсах «от новичка до профессионального программизда за 3дня» освоили очередную сакральную концепцию применимую в одном единственном языке/фреймворке; IDE выбираются не за поддержку тех или иных возможностей, а за темную или светлую тему оформления; дизайн и работу элементов интерфейса определяют «груше-дизайн-ХУДОжники», которые никогда не будут пользоваться своим «творением» за пределами презентации;… (продолжать?).
olegchir Автор
04.12.2018 15:23Хочу поопонировать вот тут. Если неофиту не нравится писать на языке, он им пользоваться не станет. Через какое-то время старый язык просто умрет вместе с поколением предыдущих пользователей. Сценарий вполне реальный, только вот он не кажется… справедливым, что ли. Если уж ты что-то приручил — например, написал свой язык и специально сделал его популярным — наверное, стоит о нём заботиться.
Guitariz
04.12.2018 16:00+1Не соглашусь. Задачи языков программирования — очень разные, и уж точно не регламентируются третьими в кулуарах обсуждений на тематических форумах.
С другой стороны, Kotlin позволяет писать гораздо быстрее. А еще у него из коробки идет nullSafety, что автоматически принуждает разработчика думать о том, что и куда он сует. И много других вещей, позволяющих писать более гибкие и продуманные сценарии поведения приложений.
При этом никто не кричит, что то же самое можно сделать на Java — просто в Java nullPointer пропустить можно по невнимательности, а в Kotlin — намеренно приколотив гвоздями неопциональное значение. Заметьте, никто не навязывает парадигму — хотите, пишите по старому. Более того, хотите, юзайте Java — она тоже совместима с Kotlin. Нет — все равно будем топить любое новшество просто потому что «у меня есть инструмент, я им решаю свои задачи, изучать новые инструменты мне не надо».dplsoft
04.12.2018 22:52Задачи языков программирования — очень разные, и уж точно не регламентируются третьими лицами в кулуарах обсуждений на тематических форумах.
гм… при чем тут «регламентирование»?
мы обсуждаем вектор классификации языков программирования, которые претендуют на «хоть сколько то» практическое применение. надо же как то оценивать и сранивать направленность языков и возможность их применния?)
и где и как вырабатывать такую классификацию, если не в обсуждениях между специалистаи в паблик форумах (хабр же не кулуарное обсуждение, согласитесь?)
С другой стороны, Kotlin позволяет писать гораздо быстрее. А еще у него из коробки...
стоп стоп стоп ))) пАгадите… при чем тут котлин? я нигде не наезжал на котлин, и не упоминал его, ведь правда? заметьте.
я поднимал проблему обоснованности появления новых языков, и то, что причины появления подавляющего числа языков не имеют прямых технических оснований — фичи большей части (имхо, если не подвляющего числа) «новоделов» не решают каких либо технических проблем, а скорее отражают взгляд авторов на мир и их собственную вкусовщину. с попеременным успехом это находит отклик в сердцах неофитов или склонных к чувственному восприятию мира людей. имхо.
я рад за тех кому это нравится. серьезно. но мне, признаться, несколько «все равно», из коробки там пара фич или нет… ну честно, извините. я настолько наслышан от предыдущих проповедников о прелестях их новых языков, и настолько видел, «куда эти языки ушли» вопреки рекламным проповедям, что простите, я вам, конечно, верю, но в вашу веру обращаться не буду, как минимум пока не будет на это технической необходимости. а технических оснований я не вижу — НАМ переводить джава проекты на котлин дорого и рискованно. не те ставки. не курсачи пишем.
я рад что вам это зашло, и что вам нравится (судя по вашему посту), но я вот, стараюсь выбирать средства разработки не по удобству или вкусовщине, а по степени пригодности для решения возникающих задач.
для андроида, возможно когда нибудь и переключусь с джавы, но пока вот более заходит Qt, просто потому, что более кросплатформенно.
и если говорить про котлин и моё «имхо»(если вы пытаетесь меня в чем-то убедить) то вот вам мое имхо: извините, не обижайтесь, но я не вижу чем котлин принципиально отличается от другиз jvm-языков.
ну вот сами посмотрите:
* был груви? был. где он? мода прошла, груви уходит на обочину истории оставясь узким нишевым языком для гиков.
* была скала? была. где она? мода прошла, скала уходит на обочину историию оставаясь нишевым языком для гиков.
* был клюжЮр? был… где он?..
и так далее — там еще в ряду, если верить википедии jPython, jRuby и далее…
и котлин ничем принципиально о них не отличается. извините, имхо.
если в чем и есть отличие ситуации с котлином, так это только то, что гуглю нужен «запасной аэродром» из-за постоянных нападок оракла. ни больше ни меньше, и это ни разу не связано с прелестями языка. это ситуация, «так карты сложились». они плотно дружат c jetBrains и «почему бы нет», я полностью понимаю выбор гугля…
но прошу вас, не надо рассказывать про прелести языка… если завтра гугль и оракл достигнут мирного соглашения — котлин с 90% вероятностью пойдет на обочину истории, как и его предшественники. в нишевость, в гиковость. и там ему пусть будут рады, я рад за тех кому это зашло.
все равно будем топить любое новшество просто потому что «у меня есть инструмент, я им решаю свои задачи, изучать новые инструменты мне не надо».
«не надо навешивать на меня ваших крокодильчиков» ) я такое не говорил.
я за разумные новшества, обоснованные, не приводяшие к технической неоднозначности или непонятности для разработчиков. я ЗА новшества. но за взвешенное к ним отношение.
давайте поясню на примере джавы: диамонд синтаксис — классная вещь, потому что понятно
«что пропускается» и где смотреть что пропущено, код по прежнему однощначен. это было хорошее нововведение.
А вот лямбда функции — «полнейшая фигня»)) очень сомнительно. потому что без IDE, читая текст примера кода на сайте — вы можете только догадываться о типах передаваемых параметров. это было плохое нововведение. из исходного кода делись определения тип аргументов и какие они долджны быть — из текста исходного кода не понятно.
Или вот взять проблемы перехода с 6й джавы на 7ю. Болезненно для многих, особенно банковских и др интерпрайз систем, да. Но извините, доступ к приватным методам класса через рефлекшен — это не просто не правильно, это не описано в стандарте, и по сути, сродни багу платформы. Это и убрали в 7й джаве, но сохранили обратную совместимость в рамках описанного в стандарте поведения.
понятно мое отношение к новшествам? )
(а вот теперь я буду наезжать на котлин )
Кстати, где уочередного убивцы джавыкотлина — стандарт языка, описание поведения, процесс сертификации сторонних компиляторов на предмет совместимости со стандартом? котлин, вопреки заявлениям авторов языка, не сможет заменить джаву, как минимум в интерпрайз секторе, пока не сможет предоставить аналогичных стандартов и воплотить гарантии сохранения обратной совместимости в виде явного стандарта и процесса сертификации сторонних компиляторов.
извините — я это у всех проповедников новых языков программирования спрашиваю, вы не первый.
не обижайтесь, но давайте с котлином подождем года 2 или 3. вдруг там оракл и гуглем помирятся?)
или вы согласитесь со мной, в моем понимании, "почему джава успешно пережила не один сезон «убивцев джавы», и почему котлин, сам по себе, ни разу не исключение". или котлин займет нишевое положение эксклюзивно-платформенного языка, так же как свифт с иосом) — посмотрим, время покажет, не торопитесь, в общем.
вам заходит, — классно. пилите свои проекты, глядишь толк из этого выйдет. но не надо агитировать моих стажеров или меня. и хайп вокруг языка — ну прошу, не надо вот этого.., столько раз уже было, ну надоело, честное слово… глупое это дело… да и показывает вас с не лучшей стороны.
мы сами к вам придем коли потребуется.
спасибо что дочитали. удачи вам.Guitariz
05.12.2018 09:45+1Первый пост просто взрыв ниже поясницы, потом 2 страницы о том, как ты за кого-то радуешься, а в конце «мне в общем то все равно». Сильно, очень сильно.
Если ты в треде о котлин пишешь длинную тираду «я не о котлине вообще писал» — дальше можно не распинаться, слушать ужа на сковороде никто не будет.dplsoft
05.12.2018 13:16Я конечно дико извиняюсь )) но вот разве в коментарии к статье о котлине нельзя написать, что "автор на примере конкретного перечня проблем в своей стаье, на самом деле иллюстрирует серьезную (мега)проблему _вообще_, которая имеет место быть не только с котлином"?
Именно это я в первом посте и написал. разве нет?
а вы начали воспринимать это как оскорбление колтина, попытку регламентирвоать назначение языков, порывались засыпать меня перечнем «фич из коробки»… ну и зачем так? ))
ладно. проехали. Нравится обсуждать конкретику, без попыток сопоставить с ситуацией вообще за пределами отдельно взятой узкой темы — ок, я не против, но я не с вами в данном направлении. Мой комментарий совсем не про конкретные микрофичи языка. не поняли друг друга, бывает.
vsb
04.12.2018 17:05Пишу на Kotlin небольшие программы (1000-10000 строк) для работы с БД. Чаще всего просто main, иногда с простым веб-сервером. Пишу уже года 3, наверное. Прокомментирую некоторые пункты и добавлю свои претензии.
> Котлин даёт одинаковый API для коллекций и сиквенсов, из-за чего люди злоупотребляют цепочками map/filter на коллекциях, создавая кучу промежуточных неленивых копий. Стримы в джаве специально введены для различия между ленивой и неленивой коллекцией. Да, есть инспекция в IDE для этого — потому что инспекции призваны исправлять недостатки языков.
На мой взгляд это удобно, т.к. stream() на каждый чих писать неудобно. Чаще всего мои функциональные вызовы короткие и состоят из одного-двух вызовов, тут stream просто лишний. В частности в Java я его стараюсь избегать как раз из-за этого, не люблю включать тяжеловесную машинерию и создавать лишние объекты там, где можно обойтись без них. В Kotlin есть выбор и это хорошо.
> Кстати, об IDE. Насколько хороша поддержка Kotlin в IntelliJ IDEA? Она действительно лучше, чем для Java?
Она хороша, но она не лучше, чем для Java. Впрочем я пока не сталкивался с тем, чтобы мне чего-то серьёзного не хватало. Бывают какие-то мелочи. Сейчас даже и не вспомню, что именно, вроде не упрощало булевые выражения, но сейчас проверил — упрощает.
> Котлин форсит использование it, что приводит к нечитаемому коду. Что-нибудь типа seq.map { it -> foo(it, 1); }.map { it -> bar(it, 2); }.filter { it -> it.getBaz() > 0; }. Что это вообще было? Имена переменным даны не зря! А тут получается монолог вроде «Возьмём это, прикрутим к нему то, потом его закрутим и если оно стало больше того, то наденем сверху шарнир».
Ничего Котлин не форсит. Хочешь давать — давай. Не хочешь давать — используй it. Я обычно даю, но иногда it читается вполне естественно.
> От интеропа с джавой кровь идёт из глаз. А тут всякие JvmStatic и JvmName, и код превращается в цирк с конями.
Уточню: от интеропа в направлении Kotlin -> Java, причём если ты хочешь именно некий идиоматичный интерфейс на Java. Если тебе достаточно просто того, чтобы человек на Java мог вызвать код на Kotlin, то проблем нет. Никакие JvmStatic и JvmName не нужны, и без них всё работает. В направлении Java -> Kotlin всё почти идеально.
> Автоматические геттеры/сеттеры с добавлением английского слова get и первой буквой проперти в большом регистре (видимо, в локали ENGLISH? Ведь регистр букв системно-зависим) — это страшно.
Что страшного? Это называется Java Beans и это в жаве как минимум с версии 1.2. Придираться к локали это вообще странно. Нормальные люди пишут код в ASCII. Да и не в ASCII оно будет работать, только большой буквы не будет, будет getсвойство.
> Давайте ещё навалим про библиотеку. Нафига в стандартную библиотеку языка, который поддерживает дата-классы, включили пары? Это ж прямое поощрение плохого кода.
Мне удобно. Редко, но полезно. Там ещё и Triple есть. У меня в каждом проекте на жаве был самописный Pair. Очень удобно, что тут он есть в стандартной библиотеке. А дата классы я вообще почти не использую, по-мне довольно бесполезная фича.
> Очень странный момент — возможность не указывать возвращаемый тип метода (особенно публичного).
Для коротких однострочных методов бывает удобно. А язык пишут не только для авторов библиотек, которым никто не мешает указывать что угодно. Опять же язык даёт возможность выбора и это хорошо.
Добавлю свои претензии.
Самое главное, это то, что развитие языка после релиза практически остановилось. Из фич, достойных упоминания, за почти 3 года были выпущены только корутины. Создаётся ощущение, что упор делается на native и javascript, который лично мне вообще не нужен, меня интересует исключительно JVM-версия. Это очень печалит. В языке есть много мелочей, которые можно улучшать, банально те же литералы коллекций, писать listOf это маразм, который я понимал на выходе 1.0, типа зарелизим хоть что-нибудь, а потом переделаем, но 3 года спустя уже не понимаю.
Нельзя создавать private классы с одинаковым именем в разных файлах в одном пакете. При этом функции, переменные создавать можно. Ограничение исключительно искусственное, т.к. функции и переменные уходят в Java-класс вида ИмяФайлаKt, а классы остаются как есть, хотя никто их не мешает сувать в тот же класс (они же приватные, их всё равно никто не видит за пределами этого класса).
Нет package-области видимости. Я не создаю разные модули на каждый чих, у меня 1 проект = 1 модуль, а компоненты и слои разделены пакетами. Очень неудобно иметь общую облатсь видимости.
Частенько приходится вводить лишние локальные переменные тупо потому, что smart cast не работает с полями класса. Порой городишь прям кучу левого кода только чтобы удовлетворить null-чекер. Ну или некрасивый !!.. Оба варианта плохи.
Нет деструктуризации по имени. Собственно это одна из главных причин, по которым я почти не пользуюсь data-классами. Код data class Point(val x: Int, val y: Int) val p = Point(1, 2) val (y, x) = p println(x) выведет 2.
Нет try-with-resources для нескольких ресурсов. А в Java это частенько. Открыл файл стрим, открыл его буферизовано, открыл другой файл стрим, открыл его буферизовано, обработал. 4 ресурса, 4 вложенности с use { }. В принципе решил для себя велосипедиком, но это должно быть в библиотеке.
Есть некоторые приколы. Например == использует equals, который работает совсем не так, как ожидает пользователь, например для BigDecimal. Поэтому BigDecimal(«1.00») != BigDecimal(«1.0»). В принципе не существенная проблема, но могли бы и обёртки для таких случаев добавить.
Про интероп — очень не нравится то, как он в итоге сделан, с платформенными типами. Проанализировать байткод JDK и расставить Nullable это абсолютно тривиальная задача. JDK никуда не денется, он, так сказать, статичен. Добавить инструмент для других библиотек тоже можно. В итоге был бы куда более приятный интероп. Сейчас весь хвалёный null safety летит к чёрту при взаимодействии с любой библиотекой на Java. Лично у меня NullPointerException-ов полно.
В языке нет иммутабельных коллекций, хотя бы в виде интерфейсов (не путать с ReadOnly).
valery1707
04.12.2018 18:16+1Есть некоторые приколы. Например
==
используетequals
, который работает совсем не так, как ожидает пользователь, например для BigDecimal. ПоэтомуBigDecimal("1.00") != BigDecimal("1.0")
. В принципе не существенная проблема, но могли бы и обёртки для таких случаев добавить.Тут всё как раз просто и логично и проистекает из начального тезиса:
==
используетequals
Всё остальное зависит от реализацииequals
в конкретном типе.
В данном случае эти действительно два разных числа и, кстати, они не равны поequals
сBigDecimal("1")
, и пользователь, раз уж использует специфические классы, должен об этом знать.
Это не проблемы языка.vsb
04.12.2018 18:42Причина понятна, но проблему это не убирает. Язык может предоставить обёртки вокруг этих классов, либо использовать для них другой метод для equals (в общем-то логично, если класс реализует Comparable, использовать compareTo). Например в Scala выбрали первый вариант и там такой код работает нормально.
valery1707
04.12.2018 18:58+1В общем случае
Comparable
это про порядок, а не про идентичность и никто не гарантирует что еслиx.compareTo(y) == 0
, то иx.equals(y) == true
.
В JavaDoc Comparable.java описание контракта декларируетIt is strongly recommended (though not required) that natural orderings be consistent with equals.
Так что использовать результатcompareTo
для идентичности так себе идея и правильно что её не используют.
А обёртки могут и появиться в следующих версиях или уже присутствовать в библиотеках. Это мало связано с языком как таковым.
Borz
04.12.2018 19:20+2А как быть с теми же Double? которые и в Comparable могут "врать" и приходится указывать допустимую погрешность вида "d1-d0<0.01", при которой мы считаем, что эти два объекта ещё "равны".
Так что да, "==" есть equals и как уж там реализует его конкретный тип — проблема этого типа, а не языка.
ldss
05.12.2018 01:39+3В статье легко можно заменить жаву на vb, а котлин — на c#, получится то же самое
Точно так же, когда в доднете появились лямбды и var, народ выл, что код станет непонятным. Прошло сколько-то лет, и уже никто не мыслит себе разработку без них, хотя вот как раз таки с дотнетовскими closures выстрелить себе в ногу проще простого
Лично для меня переход с c# на жаву был крайне болезненный, после .net 4.7 смотреть на откровенно убогие дженерики и ужасные streams было мучительно больно. Плюс совершенно безумная verbosity жавы — каждый раз, когда я писал код, возникало ощущение, что обьясняю что делать умственно отсталому ребенку.
А вот котлин стал натурально лучом света, причем некоторые вещи сделаны даже лучше чем в дотнете.
Иными словами, котлин — удобный и да, сложный тул для профессионалов. Но ставить ему это в в недостатки — это примерно как винить автомобиль за повышенную опасность в сравнении с телегой на конной тяге.ad1Dima
05.12.2018 12:04А вот котлин стал натурально лучом света, причем некоторые вещи сделаны даже лучше чем в дотнете.
не холивара ради, а интереса для. А какие?mayorovp
05.12.2018 12:31+1Вот мой список, в порядке от более желаемого к менее:
- Инлайн-лямбды;
- Null Safety;
- дата-классы, которые в C# уже четвертую версию языка обещают да ввести не могут;
- Delegated Properties;
- Неизменяемые переменные (val);
- Корутины без await.
ad1Dima
05.12.2018 12:57Null Safety;
Вот из-за этого я очень жду C# 8.0
Корутины без await.
Вот это выглядит как костыль, как по мне. Если это так классно, то зачем IDE отдельно помечает такие вызовы?mayorovp
05.12.2018 13:44Да пусть лучше их IDE помечает, чем я сам и вручную! Впрочем, и на последнее место эту фичу я тоже не просто так поставил.
ldss
05.12.2018 18:08Вот из-за этого я очень жду C# 8.0
в котлине null safety реализована через, вобщем-то, отсутствие классического null. В К. это обьект типа Nothing?, т.е., можно смело писать null.toString(), например
Интересно, как дотнет с этим справитсяad1Dima
05.12.2018 18:34null.toString()
в obj-C в купе с его динамической типизацией это вызывало проблемы в рантайме.
C# пошёл по пути swift, в проекте можно поставить флаг, и все переменные станут не нулабельные, пока явно не скажешь это. string? s = null;ldss
05.12.2018 18:52ну это ж явный костыль
ad1Dima
05.12.2018 19:06не знаю, с моей точки зрения — Nothing, как вы его описали, выглядит как костыль. Хотя вот в этих примерах он интереснее
ldss
05.12.2018 19:13с моего имха — это скорее к давнему спору null vs object
Они решили проблему в пользу(частично) object. T.e., null — это обьект. В то же время, это часть системы типов, часть языка.
Флаг же в проекте(как в свифте) вызывает у меня ассоциации с древним рантайм кастом в с++(dynamic_cast), когда надо было подключать доп. библиотеку, чтоб его делать:)ad1Dima
05.12.2018 19:18хм. Смущает сам флаг? ну так есть тонна кода, которая написана без учёта этой особенности. В свифте флага нет.
Ну и вот в этой презентации про null safety в Котлин как раз описывается то, что будет в С# 8 и как это есть сейчас в Swift…
www.hwsw.hu/kepek/hirek/2017/08/hwswfree_20170829/03_Farago_Janos-nullsafety_in_kotlin.pdfldss
05.12.2018 19:26ну, я в общем представляю, как оно в котлине, я на ем пишу:)
Флаг меня смущает тем, что это какая-то внешняя проверка, на которую, вобщем-то, полагаться нельзя. Один дев будет кодить с флагом, второй без, и либо у одного проект не будет компилироваться, либо вылезут артефакты на рантайме.ad1Dima
05.12.2018 19:30Один дев будет кодить с флагом, второй без, и либо у одного проект не будет компилироваться, либо вылезут артефакты на рантайме.
Эм… проект-то тоже в системе контроля версий. Если один включит, а другой выключит, то не будет компилироваться у кого-то. Ну, если один будет на одной версии языка писать, а другой на другой — тоже самое получится.
Флаг нужен для того, чтоб braking changes не ломали проект при переходе на новую версию языка (как это у свифт постоянно выходит). Никто не хочет рефакторить старый код, который работает.ldss
05.12.2018 19:39Никто не хочет рефакторить старый код, который работает.
какая тогда польза от этого флага?ad1Dima
05.12.2018 19:41Для нового кода, для кода который готовы рефакторить. Я постараюсь включить это в своём проекте.
ldss
05.12.2018 19:43ну а с К. ничего включать не надо, все уже есть.
ad1Dima
05.12.2018 19:50+1Просто в K она была изначально и нет тон легаси кода?
Включили галку, внутри вашего проекта все фичи есть, если что-то приходит снаружи, то оно nullable. всё то же самое, что в K, просто существующий код не сломается, если вы этого не захотите.
Представьте, если бы в котлине вдруг решили, что await перед корутиной всё же надо писать, и включили это новое поведение по-умолчанию для вашего кода. хорошо бы было?ldss
05.12.2018 19:58Включили галку, внутри вашего проекта все фичи есть, если что-то приходит снаружи
под «снаружи» я имею в виду жава классы в том же проекте/пэкедже. Проект один и тот же, иначе говоряad1Dima
05.12.2018 20:02в .NET нельзя просто так взять и писать исходники сразу на двух языках. Их придётся по разным проектам разложить.
А объекты из подключённых библиотек, тоже будут опционалами, если библиотека не поддерживает данную фичу.
Ещё раз, галка нужна для тех, кто хочет использовать новые фичи c#, новый компилятор и рантайм, но не переписывать существующий код на опционалы.ldss
05.12.2018 20:07в .NET нельзя просто так взять и писать исходники сразу на двух языках
Я знаю, и именно об этом и говорю. Перейти с упоминавшихся миллионов строк легаси на жаве на котлин иначе было бы просто невозможно, никто б не стал так рисковать. А так — берешь старый прожект и просто пишешь новые фичи на новом языке. Ну или конвертишь старые классы в К. — это несложноad1Dima
05.12.2018 20:12вот у вас такой галочкой является сам язык.
А тут, уж простите. И легаси код на c# и новый на C# как понять где string s, может принимать null, а где нет? Галкой: вот в этом проекте string s — нублабельно, а вот в том изволь писать string? s;
пишешь новый код в новом проекте, или рефакторишь старые классы — это не сложно.ldss
05.12.2018 21:01очень просто, если прожект на жаве и котлине, у последнего все входные параметры паблик функций/пропертей д.б. обьявлены как nullable
ps но офенс, но мне кажется, вы не очень хорошо знаете К. Это я не высокомерно, это к тому, что часть вопросов отпадает по мере изученияad1Dima
06.12.2018 06:44Я уже давно понял, как там в котлине. Я пытаюсь объяснить зачем галка, когда мы говорим не про два разных языка, а про две версии одного и того же языка и braking changes между ними.
mayorovp
05.12.2018 20:24«Просто так», может, и нельзя — но это ж ограничение конкретного SDK для msbuild, а не дотнета вообще.
PsyHaSTe
05.12.2018 19:37-1Если бы это не сделали опциональным флагом, майкрософт бы закидали тухлыми яйцами, а новая версия попала бы в бан во всех компаниях.
Никто не будет переписывать миллиарды строк кода, особенно когда фикс совсем нетривиальный.
К слову, это еще и не ошибкой, а ворнингом будет.ldss
05.12.2018 19:43Ну вот с К. это получается. В том смысле, что внутри *.kt у меня все фичи null safety есть; а то, что приходит снаружи, из жавы, помечается как nullable type(не автоматически, увы, но тем не менее)
Это, кстати, обалденная фишка К. — в одном не то что проекте, в одном package могут быть классы как на жаве, так и на котлинеPsyHaSTe
05.12.2018 19:44-1Как вы это будете дружить с библиотеками, написанныйх под 2.0 и которые знать не знают про то, что они должны не нулл возвращать?
ldss
05.12.2018 19:56Это может стать проблемой, но она решается просто — если метод/класс в котлине принимает параметр снаружи, от жавы, он д.б. обьявлен как nullable type. A дальше уже компайлер не даст его использовать без проверок
PsyHaSTe
06.12.2018 11:14+1Мы котлин обсуждаем, или как в C# 8.0 сделали?
Вам говорят, как в котлине сделать нельзя, потому что в котлине — так, в жаве — так. Как отличать одно от другого в дотнете — непонятно. Вот, сделали флаг. Чем плохо?
PsyHaSTe
05.12.2018 19:36-2Все же не вижу пользы в 6 пункте. M:N трединг обычно оборачивается только головной болью.
mayorovp
05.12.2018 20:21А какие есть альтернативы таскам? Реактор использовать неудобно, а на миллион потоков не хватает системных ресурсов.
PsyHaSTe
06.12.2018 11:15Я не про таски, я про треды. Таски хороши как абстракция поверх редов.
А реактор кстати мне вполне понравился, потому что часто бывает задача выбросить таску, которая уже не нужна. В дотнете (и других языках) приходится вводить костыли типа CancellationToken'ов, а с реактором достаточно выкинуть таску и она больше не будет ничего дергать.mayorovp
06.12.2018 11:23Так все-таки, что вы предлагаете для массового использования вместо проактора и асинхронности на async/await?
PsyHaSTe
06.12.2018 11:57-2Да их и предлагаю.
Отсутствие await сильно напрягает.
Например, я затащил в свое время разработчиков C# 5.0 на обсуждение
введения async/await в расте, и там был интересный момент озвучен:
We thought about precedence a lot with 'await' and we tried out many forms before setting on the form we wanted. One of the core things we found was that for us, and the customers (internal and external) that wanted to use this feature, it was rarely the case that people really wanted to 'chain' anything past their async call. In other words, people seemed to strongly gravitate toward 'await' being the most important part of any full-expression, and thus having it be near the top. Note: by 'full expression' i mean things like the expression you get at the top of a expression-statement, or hte expression on the right of a top level assign, or the expression you pass as an 'argument' to something.
The tendency for people to want to 'continue on' with the 'await' inside an expr was rare. We do occasionally see things like(await expr).M()
, but those seem less common and less desirable than the amount of people doingawait expr.M()
.
This is also why we didn't go with any 'implicit' form for 'await'. In practice it was something people wanted to think very clearly about, and which they wanted front-and-center in their code so they could pay attention to it. Interestingly enough, even years later, this tendency has remained. i.e. sometimes we regret many years later that something is excessively verbose. Some features are good in that way early on, but once people are comfortable with it, are better suited with something terser. That has not been the case with 'await'. People still seem to really like the heavy-weight nature of that keyword and the precedence we picked.
So far, we've been very happy with the precedence choice for our audience. We might, in the future, make some changes here. But overall there is no strong pressure to do so.mayorovp
06.12.2018 12:06В таком случае я не понимаю причем тут M:N трединг.
PsyHaSTe
06.12.2018 12:15M:N трединг это когда средствами языка ну никак не создать отдельный тред. Когда нет ничего уровнем ниже «запустить асинхронную таску». Недостаточно контроля, одним словом.
khim
06.12.2018 12:37К сожалению современный Linux объединяет недостатки модели N:1 и M:N.
Вернее ядро-то нормально поддерживает и M:N и N:1, но стандартная библиотека форсирует режим, когда N:1 треды эмулируют M:N. Сигнал отдельному потоку не послать, остановить его тоже нельзя и так далее. Всё в угоду тому, чтобы программы на #%#%^^%*& солярисе с M:N тредами тоже работали…
mayorovp
06.12.2018 12:48В таком случае при чем тут корутины без await? Их наличие как-то мешает создавать потоки или что?
ldss
05.12.2018 18:05Уже и ответили:)
Что лучше в моем хитпараде — это имплементация high-ordered functions
T.e., чтоб обьявить функциональный параметр в сигнатуре другой функции я пишу что-то вроде
fun printFiltered(list: MutableList<String>, filterFunc: (String) -> Boolean)
т.е., не надо использовать Func<> or Action<>, обьявление более натуральное
Что еще лучше — наличие let и прочих. Дотнет сделал гигантский прыжок с ?., но на этом и остановились. Вернее, это все есть в f#, но для него нужен отдельный проект, что не очень удобно
Убрали декларацию типа из foreach, ну и прочее
Это навскидку
Т.е., парни просто пошли чуть дальшеad1Dima
05.12.2018 18:41Если говорить о фичах, сокращающий код, то в c# с недавних пор есть
if(int.TryParse("100500", out int i) && i > 200) { //... } foreach(var (key, val) in dictionary) { }
и всякие другие.ldss
05.12.2018 18:53pattern matching, да, отличная штука
а про foreach не знал, спасибо!ad1Dima
05.12.2018 19:00про foreach не знал, спасибо!
на самом деле я немного слукавил. Для того, чтоб этот код сработал со словарём, нужно дописать метод-деконструктор. По умолчанию он есть только у кортежей.
public static void Deconstruct<K, V>(this KeyValuePair<K, V> pair, out K key, out V val) { key = pair.Key; val = pair.Value; }
(в C# 7.2 можно out заменить на in, получатся readonly reference)
guai
05.12.2018 15:53А я не понимаю, зачем они все классы final сделали по дефолту. Может кто-нибудь рассказать?
Делаем классы финальными по дефолту.
Перестают работать все instrumentation тулзы для явавских классов.
Пилим особый сборочный костыль, который делает их обратно не-final, основываясь на списке аннотаций.
…
Профит.
Зачем?
Я за свою карьеру несколько раз натыкался на проблемы из-за излишне натыканных final'ов. Есть класс, и мне надо такой же, но с перламутровыми пуговицами, один метод достаточно было перекрыть — а класс финальный. Облом.
И ни разу не натыкался на проблемы из-за того, что что-то излишне открыто. Даже не представляю, как такая проблема может выглядеть. Есть же языки, где инкапсуляции в принципе нет — и ничего.khim
05.12.2018 17:36Дурацкий вопрос: вы когда-нибудь писали хотя бы одну библиотеку, которой, потом, пользовалась бы 1000 человек? Или хотя бы 10 — но не знакомых с вами?
Поверьте, если бы писали — таких странных вопросов бы не задавали.
И ни разу не натыкался на проблемы из-за того, что что-то излишне открыто. Даже не представляю, как такая проблема может выглядеть.
Очень просто: вы меняете реализацию — и получаете 100500 жалоб на но, что у кого-то отвалился «такой же, но с перламутровыми пуговицами».
Простейший пример — класс как-то обрабатывает массивы и отдельные элементы. Вы перекрыли метод обработки отдельного элемента, а в новой версии обработку массива векторизовали и она функцию обработки отдельного элемента больше не вызывает. Вы будете себя винить или «криворуких разрабов», когда у вас после обновления всё перестанет работать?
Есть же языки, где инкапсуляции в принципе нет — и ничего.
Да, ничего. В смысле — ничего хорошего. Можете поинтересоваться у разработчиков фронтэнда о количестве костылей, которые они наизобретали в попытках запретить-таки людям создавать «такие же, но с перламутровыми пуговицами» сущности. Количество угроханного на это времени и сил исчислению не поддаётся.guai
05.12.2018 18:31Писал и пишу. И всё еще не понимаю прелести передать юзерам моего кода сообщение «обрати внимание, версия поменялась, поведение тоже» через вставление им палок в колеса.
Куча других способов есть для этого — ченджлоги, semver, @Deprecated навтыкать.
final был бы более адекватным в виде аннотации. Всё равно в яве куча способов его обойти, только геморройных.
Это как повесить здоровенный замок на хлипкую дверь сарая.khim
06.12.2018 11:34+1final был бы более адекватным в виде аннотации. Всё равно в яве куча способов его обойти, только геморройных.
Ну если вы его явно обходите, то вы должны понимать, что делаете что-то неправильное и ССЗБ.
И всё еще не понимаю прелести передать юзерам моего кода сообщение «обрати внимание, версия поменялась, поведение тоже» через вставление им палок в колеса.
Это не «передача сообщения». Это чёткое отделение вещей, которые они могут использовать, от подробностей реализации, на которые они полагаться не должны.
Посмотрите, скажем, на то, что bionic делает, чтобы через его хендлы никто не залазил во внутренние структуры. А когда-то давно наивные чукотские вьюноши просто отдавали ссылку на внутреннюю структуру… в которую куча разрабочтков немедленно начала лазать своими грязными лапками.
Это как повесить здоровенный замок на хлипкую дверь сарая.
Тут согласен. Но поскольку разработчики Kotlin сараем не владеют, то это лучше, чем ничего. Если не поможет — начнут укреплять стены изнутри (опять-таки смотри bionic)…guai
06.12.2018 12:44«Это чёткое отделение вещей...»
А @Deprecated, например, чем менее чёткое отделение? Имхо, ничем. Только его легко заигнорить, если надо. А с final'ом возня будет.
И в обоих случаях пользователь кода должен понимать, что он ССЗБ. Да вообще всегда это должен понимать, когда взял другую версию, и не погонял тесты хотя бы.khim
06.12.2018 14:56А @Deprecated, например, чем менее чёткое отделение?
Причём тут вообще @Deprecated? Как с его помощью запретить перекрывать метод? Не вызывать (это-то как раз нормально), а перекрывать?
Как вам уже написали: если вы не полагаетесь на то, когда и как сам объект вызывает свои методы — то вы можете использовать class delegation. Если же вы перекрываете методы так, что class delegation не работает — ты вы, автоматически, лезете во внутреннюю кухню класса. Что Kotlin, по умолчанию — вам запрещает. И правильно делает.
Да вообще всегда это должен понимать, когда взял другую версию, и не погонял тесты хотя бы.
Ах… не хочу ругаться матрм. То есть для вас нормально, что новая версия библиотеки может что-то сломать?
Вот и выросло поколение, которые попросту забыло для чего существуют разделяемые библиотеки, раздельная компиляции и прочее.
Вообще-то ситуация, когда я беру новую версию библиотеки (о которой автор программы не имеет даже представления), заменяю её на тестовой системе — и через пару дней тестирования запускаю в production — нормальна и правильна. Сбои должны быть исключительно редки. Ситуация, когда вам нужно гонять тесты и что-то править при обновлении библиотеки — это нештатная ситуация! Да, так иногда случается, когда люди не читают документацию (хотя бы знаменитая история с memcpy), но это должно случаться очень-очень редко, буквально раз в год! Это не должно быть нормой!guai
06.12.2018 15:06Ситуация, когда вам нужно гонять тесты и что-то править при обновлении библиотеки — это нештатная ситуация
Прикалываетесь?
В прод без тестирования вообще ничего попадать не должно. Ну если у вас хоть сколько-нибудь серьёзное промышленное приложение.
Причём тут вообще @Deprecated? Как с его помощью запретить перекрывать метод?
Типа такого:@Deprecated("запрещаю перекрывать этот метод")
Что ничуть не хуже, на мой взгляд, чем:/*не декомпилируй и не инструментируй этот класс позязя*/ final class Foo {}
AlexeySoshin
05.12.2018 17:48Потому что «Favor Composition Over Inheritance». Для «перламутровых пуговиц» существует class delegation: kotlinlang.org/docs/reference/delegation.html
guai
05.12.2018 18:45Только в том случае, если б в исходном коде реализацию интерфейсов юзали. А тогда и проблемы бы не было.
Но код написали через наследование классов, а еще final'ами обмазали для верности.
mr_tron
> Говорят, все новые мобильные проекты на Андроиде пишут исключительно на Котлине.
Врут
dzigoro
конечно, что не на Котлин, то на React Native.
ZLBRSTN
React Native — легаси и прошлый век. Flutter
Ayahuaska
Как быстро во фронтенде летит время.
Один час на Земле равен 30 годам во фронтенде…
Neikist
Да ладно, флаттер нормальная технология. Самое главное что к js, html и css отношения не имеет.
alexdevyatov
Вы в слове «говно» 7 ошибок сделали