Comments 79
К динамической типизации var не имеет никакого отношения. Он не "привносит динамики" в java.
"в статически типизированные языки пытаются привнести динамики",
фраза написана так.
я не говорю что java становится от наличия var динамически типизированным языком.
Но этот синтаксический сахар в работу с java привносит одну особенность из динамических языков. Если я объявил переменную, я могу менять ее значение при рефакторинге на любой тип, не меня объявление переменной.
Либо просто не задумываться о типе переменной при ее создании.
В java так изначально не было. Подсмотрели это в динамических языках.
Такими темпами у вас Хаскель (у которого статическая типизация настолько строгая, что Яве и не снилось) окажется динамически типизированным языком.
Вы не можете после присваивания var результата функции calculate имеющей тип CalculationResult присвоить ей ничего, кроме другого CalculationResult.
Динамическая типизация означает, что одна и та же переменная может в разное время работы программы держать в себе разные значения разных несовместимых типов. А то что при этом не нужно прописывать её тип уже следствие. Нечего прописывать.
В Java/Kotlin/Scala/Rust/C++ не обязательно (где-то с рождения языка, где-то, как в Java и C++ после обновления стандарта) указывать типы переменных, но переменная имеет тот самый выведенный тип и после того как он выведен, в неё нельзя класть ничего другого.
Касательно var в Java это вопрос кодстайла. Так же как и фабрика абстрактных фабрик фабрик тоже будет ужасна с точки зрения понимания человека, но Java никак не мешает этому с первой же версии. Всё хорошо в меру.
Например, var хорошо заходит там, где тип уже упомянут в той же строке, типа каста или new. Нет нужды его дублировать ещё и в описании переменной.
А какой смысл не дублировать его, если он точно известен? Байты исходников экономить :)?
В любой ситуации если переменная может быть только строго определенного типа я ожидал бы это увидеть. И наоборот, если тип явно не указан - это намекает, что тут не всё так просто, как кажется, и следует изучить как туда что-то ещё попасть может.
Это же Java. Никому не хочется писать что-то вроде InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonPainter в одной строке дважды. Это кстати настоящее имя внутреннего класса из swing.
Да, но адепты втыкают то var как правило вместо String :).
И я уж не знаю, что там у вас происходит, но мне обычно гораздо более скромные классы попадаются по длине. Дай бог один на сто такой. Впрочем и с вашим я совершенно не виду проблемы его указать просто для того, чтобы голову людям лишний раз не морочить.
First they were like: перестаньте спрашивать нас на собесах про методы классов стандартной библиотеки, я не хочу учить их наизусть, они есть в справке!
But then: ты не указал явно имя одного из миллиона классов в иерархии домена, которые я знаю наизусть, как же я теперь узнаю, правильный ли ты выбрал API, бака, дизморалечка тебе!
Ну вот что тебе даст явное указание имени типа? Допустим, это стандартный тип: идешь и смотришь документацию по нему. Допустим, это новый тип и новый метод, который его возвращает: идешь к месту объявления метода, смотришь тип возврата (а он там всегда явный), далее идёшь к декларации типа, читаешь, возвращаешься к месту вызова метода, продолжаешь читать. Ты в любом случае сначала сбегаешь к декларации типа, явно он указан или через вывод.
Длинные строчки не влезают на экран (особенно в режиме diff). Плюс дублирующаяся информация создаёт визуальный шум. На фоне самоочевидной информации теряется сама бизнес-логика, которую обычно гораздо важнее проверить - с проверкой типов справится и компилятор, а вот корректность алгоритма он не проверит.
если тип явно не указан - это намекает, что тут не всё так просто, как кажется, и следует изучить как туда что-то ещё попасть может
Если это Java, то вы знаете, что всё просто и туда может прийти только один, пусть и не указанный явно, тип. Что-то может изменится разве что с обновлением того кода, откуда приходит значение (описание самой функции calculate), но если она что-то радикально другое начнёт возвращать, то скорее всего будет ошибка компиляции (по отсутствию методов или попытки передать в функцию ожидающую конкретный тип, а var для аргументов нельзя). А ещё у вас есть тесты, которые при радикальном изменении поведения возвращаемого значения упадут.
В отличии от языков с динамической типизацией, здесь вы уверены, что хоть тип и не указан, но он всегда один. Нет никаких граничных случаев, что в рантайме придёт то, к чему функция не готова в плане типа. Так что ломающее изменение легко ловится и компилятором, и самыми базовыми тестами.
Если отмести претензии к терминологии, то автор статьи во многом прав. Очень тяжело читать код в котором одни auto или var. Код читается гораздо чаще чем пишется, а чтобы понять что за тип приходится лазить по исходникам.
А без понимания типа теряется то, ради чего. Ооп и задумывался: Видение предметной области.
Я думаю авто/var еще более менее оправдан при использовании в функциональном программировании, когда пользуешься длиннющими итераторами с много раз параметризованными шаблонами. Там понимание типа менее важно, но все равно это бонус. Поэтому сам я редко использую слово auto.
var result = calculate(args);
а как насчёт чего-нибудь вроде
String s = calculate(args).toString()?
Тут тоже нет никаких явных типов
Так я и не говорю, что неявные типы это плохо.
Я говорю о том, что нужно позаботиться о том, чтобы код было удобно читать.
Иногда удобно записать все в одну строку и опустить типы,
иногда необходимо явно подсветить какой тип у нас есть на определенном этапе.
С появлением var перед разработчиком вариативности как писать стало больше.
Мое мнение, что писать везде var вместо полноценного типа только потому, что так позволяет компилятор и это короче - не верно.
Я говорю о том, что нужно позаботиться о том, чтобы код было удобно читать
ну, иногда удобне читать var result
вместо Optional<Map<String, List<Object>>> result
...
все так)
я и не призываю отказывать от var,
но если развить этот пример, то альтернативным решением может быть придумать нормальный тип вместо Optional<Map<String, List<Object>>>, что сделает контракт метода не отвратительным, а не просто заметать под var
вся статья исключительно про эту идею
может быть придумать нормальный тип вместо Optional<Map<String, List<Object>>>
например, class OptionalMapOfStringAndListOfObject extends Optional<Map<String, List<Object>>>
?
придумать нормальный тип вместо Optional<Map<String, List<Object>>>, что сделает контракт метода не отвратительным, а не просто заметать под var
Вся история развития ЯВУ это заметание сложности под половичок. Любая либа или фреймворк заметают под половичок на 100 порядков больше всего, но мешает вам именно вывод типов.
В Java нет алиасов типов.
Получается, либо велосипедить свой Optional или Map (если по факту нужно вернуть именно это), либо так.
Мне кажется, если так получилось, то проблема уже в архитектуре, но суть понятна.
чтобы код было удобно читать
Для этого достаточно всего лишь пользоваться IDE вместо редактора для медленных терминалов (vi) или notepad.exe. Навёл мышкой — показало тип.
Называется неявная статическая типизация. В JEP 286: Local-Variable Type Inference есть объяснение зачем. Scala, Kotlin, Go liked this.
Динамическая типизация, это когда одной и той же переменной можно присваивать значения совершенно разных типов. Вот если бы в Java можно было написать:
var x = 12;
x = "12345";
это была бы динамическая типизация.
Но нет, такое в Java невозможно. И конструкция:
var x = 12;
создаёт переменную целого типа, которой можно присваивать только целочисленные значения.
Var - не динамическая типизация, а всё та же статическая. Вся разница только в том, что вместо явного прописывания имени типа используется тип значения инициализирующего выражения, однозначно определяемый в момент компиляции. Это называется "выведение типа": оно используется во многих статически типизированных языках и не имеет никакого отношения к языкам с динамической типизацией.
В статье нигде и не написано, что java стала динамическим языком от этого.
Но фишка с выведением типов появилась, потому что опыт динамических языков показал, что так писать может быть удобно в некоторых случаях. По этому подобный сахар принесли во многие статические языки.
Вы в своей статье преподносите var как доказательство факта:
в статически типизированные языки пытаются привнести динамики
и это не соответствует реальности.
В var нет динамики. Ни в каком виде нет. Динамика - это когда что-то делается в процессе выполнения кода. Но var работает на этапе компиляции и только компиляции: статика в чистом виде.
Ваше предположение, что var появился в статически-типизированных языках под влиянием динамически-типизированных, абсолютно ошибочно. Между статическим выводом типов переменных в C#, Go, Java и полным отсутствием типов переменных в JavaScript, Python нет ничего общего.
Спасибо за конструктивный коммент.
То, что под капотом динамики нет, я осведомлен)
Видимо фраза "привнести динамики" оказалась слишком абстрактной. Под ней я как раз имел ввиду некоторые заимствования из динамических языков.
Если я все нашел правильно,
1. Первый динамический язык LISP появился в 1958 году.
2. Первый статический язык с выведением типов Meta Language появился в 1973
Но вот чем именно вдохновлялись разработчики определенных языков, наверняка уже не узнать, к сожалению, или к счастью)
1. Первый динамический язык LISP появился в 1958 году.
Но вот чем именно вдохновлялись разработчики определенных языков
Язык IPL 1956 и лямбда-исчисление Черча.
И вообще-то, ANSI Common Lisp 2ed 1991 описывает форму declare с одной из спецификаций - type, указывающей на то, что переменные в связываниях будут принимать значения только указанного типа. И это не делает Common Lisp менее динамически типизированным, а цитируя стандарт: "...носят рекомендательный характер, и могут использоваться Lisp системой для создания дополнительных проверок ошибок или более производительного скомпилированного кода. Декларации также являются хорошим способом задокументировать программу. Следует отметить, что нарушение декларации рассматривается, как ошибка (как, например, для декларации type), но реализация может не замечать этих ошибок (хотя их обнаружение, где это возможно, поощряется)."
В C# (Java от Майкрософта, считайте) var уже давно есть, и никто до сих пор не умер.
Когда это вносили в Java, ссылались на Kotlin/Scala/Go. Все эти языки имеют статическую типизацию с опциональным выводом типов. На Python или JavaScript никто не ссылался.
идея, которая больно аукнется
Так как и где оно аукается? Примеры есть? Не путайте "не знакомо/не привык" с "плохо/мешает/стреляет в ногу".
Но брать и заменять везде явное указание типа на var только потому что это теперь компилируется, это идея, которая больно аукнется
Предложение звучит полностью так.
Сам по себе var не плохой. Плохая идея использовать его бездумно везде.
Как это аукнется:
Меньше контроля за контрактами. За счет того что типы скрыты, на возникает меньше поводов задуматься о правильности контрактов и композиции.
В своей практике часто замечал проблемы в коде, именно когда на глаза попадался сомнительный тип переменной в контексте метода.
+ Общее замедление ревью
Мне все же кажется, что вы ищете проблемы там, где их нет, чтобы оправдать нежелание принять новый концепт. Самое-то ироничное, что рядом живет котлин, где могут быть огромные функции без единого объявление типов - все чисто на type inference. Я этот код ревьюю каждый день, и может быть в 1 случае из 100 я вижу какой-то сильно накуренный кусок, где явные типы внесли бы ясность. Чаще как раз наоборот: если убрать бесконечные повторения String, Int, JsonObject и т.д, то взгляд меньше спотыкается, и ты читаешь именно идею кода, а не всю эту лапшу. Естественно, вразумительный нейминг обязателен.
Я еще раз рекомендую вам обдумать идею, что такое "плохо" и что такое "незнакомо".
Автор статьи прав. Нужны явные типы. Это упрощает код.
Выведение типов - вот это лапша так лапша... Через полгода-год открываешь свой же код и уже ничего не понимаешь.
Все эти var и т.п. - ненужная абстракция.
Ну для ознакомления прошло уже достаточно времени, и да, читать код стало сложнее, если var используется где-то за пределами for(var entry : map.entrySet()), т.к. при чтении кода (по крайней мере мне) проще сканить глазами типы, написанные первым словом в строке, понимая сразу все накладываемые на переменную ограничения и особенности поведения, чем надеяться, что автор кода придумал идеальное имя переменной, такое, что при быстром сканировании кода оно не будет вызывать вопросов и вводить в заблуждение.
При этом существует вполне нормальная официальная рекомендация по использованию var, и если бы ей все следовали, всё было бы вполне неплохо. Но в большинстве своих фич, java сопротивляется "плохим" решениям разработчиков (но они конечно всё равно находят способы с этим справитьс, а тут способствует.
Под хорошо/плохо я в частности имею ввиду локальность контекста: в идеальном коде для понимания локального участка кода необходимо минимально осмотреться вокруг, пару строк вверх пару строк вниз. То, что ближе к этому - хорошо, то что дальше - плохо.
Что значит неудачный тип переменной?
Если тип неудачный, то там и логика пострадает. Что придётся всё с переменной делать через одно место. Условно, искать значение в List перебором вместо выборки из Map.
Но это будет видно и без знания типов. "Ага, он зачем-то сделал цикл в цикле с полным перебором массива, но ведь это можно было избежать, используй он Map".
А если по алгоритму непонятно, что с типами что-то не то, то скорее всего с ними всё то.
var в java, это фактически auto в C++
Тут я сразу вижу что за объект result мне вернулся, и что я могу с ним сделать
Расскажите, что вы можете сделать с объектом CalculationResult)
Как минимум перейти по ссылке на него и посмотреть что он из себя представляет.
Какие-то результаты вычислений получить...
Расскажите, что вы можете сделать с объектом var ?
Разница. Огромна.
Var полезная штука, как выше сказали, никакой динамики она ни грамма не дает. Но удобно очень не копипастить огромные конструкции типов, что очень распространено в java. Вы попробуйте объявить переменную типа Record из jooq с дженериковскими скобками в 20 полей. А понять что за переменная перед вами всегда можно наведя на саму переменную курсор, ну если вы не в блокноте кодите конечно. Не понимаю претензии к var.
В intellj есть галочка, если ее установить, то в редакторе будет добавляться тип переменной.
Я часто пользуюсь var. Меньше писать, компактнее код визуально. Если не обзывать переменную типа var retval =, как это некоторые делают, а писать понятное, что то вроде var calcResult = , то и читать легче. И потом если вдруг поменялся тип возвращаемый функцией, не надо переписывать вызовы.
Некоторые коллеги тоже жалуются, "не видно сразу, что за тип, надо мышкой наводить". Лайфхак с галочкой помог разрядить обстановку. :)
Прямо вспоминились обсуждения, когда добавлении auto
в C++. И ничего - всё устаканилось.
Людям свойственно смиряться с проблемой, побухтев некоторое время. Это не значит, что проблема решена.
А какой вой стоял, когда в C# появились анонимные типы и принесли с собой var
. Жабий var
вроде бы работает аналогично.
В C#, кстати, есть не только вывод типов через var
, но и динамический тип dynamic
, вот этим реально лучше не пользоваться без крайней необходимости.
Дорогие комментаторы
Автор был не очень точен в формулировке. Безусловно, он имел ввиду "неявную статическую типизацию". Не хейтите его - он понял всё верно)
Статья, не считая этого косяка, очень даже дельная: не стоит использовать var на каждом шаге. Стоит использовать либо в реально очевидных местах, либо вместо монструозных типовых конструкций, либо в сприптах. Но на самом деле сильно переживать не стоит, ведь обо всём напишут в кодостайле и выскажут на ревью :)
Я сейчас больше работаю с Котлин, чем с Java. В Котлине var и val с самого начала существования языка. И я хочу сказать, что никогда не было проблем с отсутствием типов в коде текста. Никогда не леплю еще и тип в дополнении при объявлении переменной. Хотя в начале плевался на такой подход с непривычки (как и на nullable ?). Но это настолько очищает код, что сейчас считаю это очень хорошей практикой. И в java программирую редко, но делаю то же самое, и очень надеюсь что эта практика скоро будет повсеместной.
Спасибо за поддержку и локализацию ошибки в формулировке)
Очень приятно, что вы увидели мой посыл и не побоялись словить минус за коммент.
P.S. большинство оплевух было поделу, но именно ваш коммент вернул мне мотивацию,
Надеюсь, что следующая статья будет менее противоречивой)
К сожалению на ревью такое трудно пофиксить. Часто в компаниях бывает практика нетоксичных ревью и переубедить кого то что в этом месте лучше написать bool или нормальный короткий тип нет никаких рычагов.
Если честно, вообще не понимаю какие плюсы от записи через var. Это и менее читабельно для разработчика не погруженного в контекст данного куска кода, и скорости написания сильно не прибавляет.
Чтобы не писать MyCoolObject obj = new MyCoolObject(). Или MyCoolObject obj = (MyCoolObject) anotherObject.
В общем, где тип и так указан в той же строке.
Ещё может быть какая-то функция (возможно, не ваша вообще, а библиотечная) возвращающая длинный тип из кучи генериков. А тебе из этого типа нужна пара полей.
Но если подумать о примере автора.
var result = calculate();
System.out.println("Elapsed time: " + result.getElapsedTime());
System.out.println("Result: " + result.getValue());
А нужно ли мне здесь знать точный тип возврата calculate? Алгоритм то и так понятен (что-то посчитали, вывели инфу о прошедшем времени и значение результата).
В момент написания кода IDE подскажет какие у него методы. CI/CD уведомит, что код компилируется (если, например, кто-то изменит тип возврата calculate и там больше не будет нужных методов). А на ревью проверяется в первую очередь общая логика.
В ТЗ был вывод времени и значения. Код выводит время и значение. Готово.
Ну я лично var использую только в двух случаях:
Когда у меня UsernamePasswordAuthenticationToken в виде типа данных (к примеру, тупо писать много, у меня строка на 100+ символов не учитывая табы получается)
Либо Map<List<String>, Map<Integer, Pair<String, String>> - к примеру что-то такое (если это ещё и в оптиональ завернуть, вообще опупеешь). P.s. Pair<> в данном случае будет являться кастомным классом.
Это не выстрел в ногу и не шаг к динамической типизации. Это упрощение жизни разработчикам в таких вот случаях. Под капотом это по факту всё те же типы данных, которые поменять нельзя.
А у нас например auto пишут даже вместо bool и int. И я могу как-то понять авто вместо int потому что это нужно думать, что там int uint32 или std::size_t, но это усложняет понимание сложности алгоритма, но const auto вместо bool непонятно совсем. Так или иначе надо продумывать заранее размеры типов и как он выведется в авто. Тут можно попасть в ловушку неправильного выведения типа при инициализации переменной
Я не говорю уже про пользовательские типы. там всегда авто.
var - это очередной жабий сахар. Сахар всегда нужно применять с умом. Если у вас var x = calculateSomething(); то это явно говнокод.
Но ведь и Something x = calculateSomething(); тоже говнокод.
Осталось только добавить: вот раньше было лучше. К сожалению, Java сильно устарел из-за свой обратной совместимости, и все фичи давно воспринимаются как запоздалая попытка догнать современные ЯП. Так же Java чересчур многословен, непонятно, зачем нужны лишние слова в языке если они не несут никакой смысловой нагрузки? Scala давно имеет более продвинутую и систему типов, var/val, и еще много чего. Читать код из блокнота/веб страницы - в чем достоинство преодоления трудностей? Это давно обрабатывают IDE/компилятор. Вывод типа - в идее достаточно навести мышь на переменную, не нужно никуда переходить.
Почему автор не прогнал статью через гпт? Гпт бы всё автору подробно объяснил бы и возможно даже и нужды в данной статье не было бы.
Бороться с var бессмысленно. Это факт. Посмотри на количество своих минусов. Единственный способ - тимлид, который запретит использование.
Подсказка в Intelij IDE поможет не сойти с ума. Однако на код ревью, будете и дальше гадать что там за тип.
Если команда адекватная, постарайтесь договориться, о каком-нибудь умном использовании var. Например, только если есть справа new. Или в маленьком приватном методе. Потому что читать простыню кода в перемешку с var и без это гг.
Дженерики, var с ними плохо работает, т.к. кастит к Object. Использование методов возвращающих T, все равно придется писать без var.
var удобен, если тип часто меняется, например был Set, а стал List, не надо трогать тип ссылки.
Если вам не все равно на ваш проект, вы уважаете тех кто читает ваш код, тех кто делает код ревью - не используйте var, или используйте с умом, не создавая дополнительную когнитивную сложность.
Сразу скажу, я не джавист. И я с большим удивлением узнал, что такая полезная штука, как type inference отсутствовала в Java до появления var.
Как человек, имеющий богатейший опыт написания кода на языках со строгой типизацией и type inference, могу сказать, что тут исключительно плюсы. Как раз читаемость и понимание кода возрастут крайне сильно, потому что километровые строчки превратятся в элегантные и короткие.
При этом и компилятор, и IDE вам мгновенно подскажут, если вы что-то не то напишете. Ничего общего с динамической типизацией тут даже близко нет.
Запоздалое обсуждение. Синтаксис var появился в Java 10, ему уже 7 лет. Да и вообще его аналог сейчас есть почти в каждом современном статически типизированном языке.
Мне вот только непонятно, кто заставляет автора делать кодревью в браузере. Видимо, это происходит под дулом пистолета.
Гениальный вопрос: зачем? Чтобы в другом месте кода стало больше? Был void чем мешал?
Везде ставлю вар и готов грызть горло каждому кто хочет старые длинные портянки на несколько линий.
Var не требует импортов, меньше диффов. Больше методов в том же arraylist тк не надо кастить в лист. Портянки с типами теперь короче. Намного более читабельный код, все переменные с 4 буквы начинаются, глазам легко. То что на пуллреквесте не видно типов - это проблема тулзов и это рано или поздно решат. В идее можно сделать чтобы видно было. В плюсах подвезли авто и люди тоже не жалуются. Это лучшая фича жавы со времён восьмерки.
Везде ставлю вар и готов грызть горло каждому кто хочет старые длинные портянки на несколько линий.
Какой грозный падаван!
Попадёте в проект, где решили не ухудшать читаемость ради популизма и будете грызть разве что в мечтах.
Var не требует импортов, меньше диффов.
Декларация локальных переменных это не единственное, что встречается в коде. Импорты если и станут меньше, то только в коде уровня «Hello, world!».
Больше методов в том же arraylist тк не надо кастить в лист.
Даже не смешно.
Если вам на пустом месте нужно приведение типов, то вы что-то делаете не так и вас рано или поздно ждёт ClassCastException
.
Импорты станут меньше независимо от var :) https://openjdk.org/jeps/511
Если вам на пустом месте нужно приведение типов, то вы что-то делаете не так и вас рано или поздно ждёт
ClassCastException
.
А можно попробовать привести умозрительный пример?
Импорты станут меньше независимо от var :) https://openjdk.org/jeps/511
В командах, стремящихся к написанию легко поддерживаемого кода запрещены на уровне pre-commit hook даже import
пакетов через *
. JEP 511 пойдёт туда же. У себя в хелловорлдах испозовать его вам никто не запретит, конечно же.
А можно попробовать привести умозрительный пример?
Любое приведение к T
, не защищённое if (obj instanceof T)
это потенциальный ClassCastException
. В реальных долго развивающихся проектах можно наткнуться и на приведение типов после проверки значения какого-нибудь свойства объекта:
if (obj.isFoo()) {
return (Foo) obj;
}
И просто приведение не к тому типу, который фигурирует в защищающем его if (obj instanceof T)
.
Это может долго работать до тех пор, пока в ходе рефакторинга не изменятся соответствующие части.
CalculationResult result = calculate(args);
Тут я сразу вижу что за объект result мне вернулся, и что я могу с ним сделать.
Плохой пример: CalculationResult
слишком общее название, чтобы понять, что с ним можно сделать. Ничем не лучше var
:)
var в java, так долго ждали, чтобы стрельнуть себе в ногу