Pull to refresh

Comments 176

По-моему, перед нами случай неполной индукции. Увидев несколько проблем в нескольких библиотеках, человек ощущает соблазн увидеть за ними большýю (и бóльшую) неприятную тенденцию в программировании на всём этом языке в целом. И поддаётся, поддаётся этому соблазну.
Сразу скажу, что лично я бы из вышеизложенных наблюдений сделал вывод о том, что AngularJS и typeahead.js сочиняли как курица лапою (мне, кстати, всё равно — я ими не пользуюсь), а создателям MomentJS не помешало бы вдругорядь продумать API (я и сам сталкивался с тем, что в API этой библиотеки кое-чего недостаёт; в частности, переводить юниксовое время в массив лет-месяцев-дней-часов-минут-секунд мне пришлось несколько извращённым способом).
Конечно, JS всячески мешает разработчикам создавать говнокод — проверками типов, иммутабельностью, только явными приведениями, богатой стандартной библиотекой, удобными тулзами для concurrency.

Ах, простите, забыл — в нем этого всего нет. Welcome стрелять себе в ноги.
Отсутствие проверки типов и иммутабельности и наличие неявных приведений — это не баг, это фича, сокращающая объёмы boilerplate code и приносящая RADость.

Отсутствие богатой стандартной библиотеки преодолевается изобилием сторонних библиотек с открытым исходным кодом.

Для concurrency есть API, причём во браузерах — один, а в Node.js — другой. Для такого многоцелевого языка, каким de facto является JavaScript, это естественно.
>> Отсутствие проверки типов и иммутабельности и наличие неявных приведений — это не баг, это фича
Конечно, а разработка на Python или Ruby медленна, печальна и полна бойлерплейта.
А, как же я забыл про апофеоз печали и страданий — Clojure.
Я, конечно же, говорю не о статической типизации, а о строгой динамической, где шаг вправо-влево — TypeError.

Про API для concurrency я, пожалуй, промолчу — даже в Python 2.6 уже можно было писать линейный асинхронный код.

Собственно, это очевидные и давно известные факты, я не понимаю, как их можно пытаться оспорить.

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

Заходим в топ говнокод.ру:
govnokod.ru/best?time=ever
На первой странице тебе и C++, и C#, и Java. На других страницах ситуация та же. Более того, говнокод.ру ведёт статистику по количеству цитат на язык программирования — C++, C#, Java вполне себе идут вровень с JavaScript.

Вывод: ваш тезис неверен.
Автор и не говорит про весь язык, он говорит про тенденции. Приходится копаться в коде различных библиотек, и действительно часто встречаешься со странными и вредными решениями. Из последнего — datatables.js, который при расширении дефолтных настроех с помощью jquery.extend, за-компанию пару раз клонирует весь массив входящих данных.
Авторам этаких решений надо бы откусить голову по самую жопу (да простит меня Ведьма Шарлотта) или, по крайней мере, посылать им такие запросы на слияние (pull requests), которые приводили бы их код к более приемлемому поведению.
Кстати, да. Совершенно верно.

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

Дело в том, что map на каждой итерации вызывает callback-функцию, передавая ей три аргумента: элемент массива, его порядковый номер, весь массив в целом.

Получаем три вызова:

  • parseInt("1", 0, ["1", "2", "3"])

  • parseInt("2", 1, ["1", "2", "3"])

  • parseInt("3", 2, ["1", "2", "3"])

И так как parseInt воспринимает второй из своих параметров (когда он присутствует и не нулевой) как основание желаемой системы счисления, то наблюдаемый эффект не удивителен. Единичной системы не существует, а в двоичной нет (и быть не может) числа 3.
Достаточно
["1", "2", "3"].map(function(item){ return parseInt(item); })

Именно такое поведение обычно ожидается человеком, пишущим
["1", "2", "3"].map(parseInt)
js > Number
[Function: Number]
js > ['1', '2', '3'].map(Number);
[ 1, 2, 3 ]
js > x = _
[ 1, 2, 3 ]
js > x[0]
1
js > typeof x[0]
'number'
Не знаю насколько это актуально в обсуждаемом приложении, но все же parseInt(x) и Number(x) могут выдавать разный результат
Number('123_')
NaN
parseInt('123_')
123
Всё понятно уже из названий: Number ожидает на вход строку-число, а parseInt пытается распознать число в строке. Подобным parseInt образом работает scanf() в C.
> ["07", "08", "09"].map(function(item){ return parseInt(item); })
[ 7, 8, 9 ]
Это Хром или последние версии FF. Ниже уже рассказали про эту классическую особенность JS: по стандарту parseInt, при отсутствии явно заданной системы счисления, строки начинающиеся с 0 интерпретирует как восьмеричные. Поэтому parseInt("09") возвращает 0.
js>  ["07", "08", "09"].map(function(item){ return parseInt(item); })
[7, 0, 0]

spidermonkey, т.е. как бы firefox
В какой-то момент FF пошел за Хромом и тоже стал всегда десятичный разбор делать. Я не знаю с какой версии это началось и как синхронизировано со Spidermonkey, но сейчас мой FF25 выдает [7,8,9] и это не первая его версия, которая так себя ведет.

Просто удивляет количество плюсов у коммента, в котором автор осознанно предложил отказаться от явного указания системы счисления. При том, что это классический вопрос и, по крайней ней мере мне, его все время задают на собеседованиях.
В общем, результат выше — это именно spidermonkey (в данном случае консольного). Действительно, проверил, Fx даёт другой вывод.

А ещё у меня (в том комменте, на который был этот ответ без указания системы счисления) оказалось целых два неиспользуемых аргумента в функции. А тут оптимизация же. (Интересно, есть ли хоть какая-то реальная разница, кроме +10 символов кода.)
Вероятно расходы как на создание новых переменных. Да и современные минификаторы, скорее всего, их удалят как неиспользуемый код.
по стандарту parseInt, при отсутствии явно заданной системы счисления, строки начинающиеся с 0 интерпретирует как восьмеричные
.

15.1.2.2 parseInt (string, radix)

The parseInt function produces an integer value dictated by interpretation of the contents of the string argument according to the specified radix. Leading white space in string is ignored. If radix is undefined or 0, it is assumed to be 10 except when the number begins with the character pairs 0x or 0X, in which case a radix of 16 is assumed. If radix is 16, the number may also optionally begin with the character pairs 0x or 0X.

Как видите, ничего в стандарте про восьмеричную систему, только про шестнадцатиричную.
UFO just landed and posted this here
Это замечательная функция, наверняка полезная в хозяйстве. Но как называть того, кто придумал дать ей имя map?
Какое имя вы хотите предложить, как более уместное?
Как раз проблема в том, что это не каноничная функциональная map, и такое название лишь вводит в заблуждение.
Кстати, единичная система счисления существует: _, 1, 11, 111, 1111,…
Она радикально отличается от всех «нормальных» систем счисления: например, в ней непредставим нуль.
Ну почему же? За нуль можно принять 1. За единицу 11, за двойку 111. Только что с таким представлением вычитать в столбик не удобно, и умножать составлением матрицы, но это обходится простым правилом — при умножении рисовать на одну строку и колонку меньше, при сложении убирать одну единичку, а при вычитании — одну добавлять, про деление можете додумать.
А что делать с отрицательными числами? У вас выходит, что «положительный нуль» и «отрицательный нуль» — разные числа?
А что делать с нецелыми числами?

В «нормальных» системах счисления ни той, ни другой проблемы нету.
Положительный и отрицательный нуль есть и в двоичной системе. Тут больше вопрос не в самой системе счисления, а в способе представления отрицательных чисел.
Мы с вами точно об одной и той же двоичной системе говорим?
В моей .., -11, -10, -1, 0, 1, 10, 11…
А в вашей?
Стоит тогда упомянуть для определённости ещё дроби: 1/10 (1/2) = 0.1, 1/100 (1/4) = 0.01, 1/11 (1/3) = 0.(10) и т. д.
Почитайте про обратный и дополнительный код представления двоичных чисел.
Не путайте двоичную систему счисления и её представление в памяти.
Я и не путаю. А написал я «Тут больше вопрос не в самой системе счисления, а в способе представления отрицательных чисел». А вы стали минусики перед числами в двоичной системе ставить.
Нет в двоичной системе положительного и отрицательного ноля. В некоторых её представлениях — есть.
Вы спорите не с тем, что я написал, а с тем что сумели прочитать.
Положительный и отрицательный нуль есть и в двоичной системе.
Это не «представление двоичной системы в памяти», это просто «представление чисел в памяти».
Двоичное число 11011010 с таким же правом представляет двоичное число -100110, с каким десятичное число 218 представляет десятичное число -38.

Модульная арифметика создана задолго до компьютеров, и представление чисел в памяти — далеко не единственное её применение.
только все-таки 0, 00, 000, 0000, …
Нет, всё-таки обычно 1.
В качестве единственной «цифры» используется «1», чёрточка (|), камешек, костяшка счёт, узелок, зарубка и др.
Собственно в том и претензия, что чтобы разобраться со многими конструкциями, встречающимися в реальном коде, нужно внимательно читать описания. И встретив ["1", "2", "3"].map(parseInt) в коде, не понятно, то ли автор не прочитал (или прочитал, но забыл) соответствующие доки, то ли сознательно использовал фичи языка для достижения того результата, который получился. Слишком много в JS неявностей и умолчальностей.
Слишком много в JS неявностей

Дык что ж тут неявного, когда это в явном виде написано по той ссылке (первая в гугле, кстати), которую я написал? Ну это все равно что сказать что «вот представляете, чтобы тронуться с места на машине надо не только давить на газ, а еще и на сцепление».

Отчасти соглашусь с тем, что то что 0 во втором параметре parseInt переводит в десятичную систему — странно. Правда странно.
В том и заключается неявность, что для понимания нужно лезть в доки. И да, пример со сцеплением очень удачный — тоже пример неявного, с которым без доков не разобраться :)
Ну… мне кажется, что язык программирования может себе позволить заставлять «пользователя» лезть в мануал.
Как и машина, в общем.
В этом месте было бы уместно ругаться на количество параметров, что бы и предотвратило бы ошибку.
К сожалению, именно parseInt почти так же часто используют с одним параметром, как и с двумя.
Да, но map пытается передать три.
Но не так же часто :)
«Но не так же часто» — это про PHP; а в JavaScript всё более-менее по-божески ещё.
Мое мнение строго противоположное. В PHP умолчаний и неявностей меньше. По крайней мере в реальном коде. Куда с большой вероятностью можно утверждать, что автор кода не учел каких-то нюансов, если интуитивно ожидаемое поведение кода отличается от реального и документированного — неочевидные «хаки» используются намного реже.
Вообще-то в PHP реальным и документированным является поведение

  • array_filter($input, $callback) — но array_map($callback, $input);
     
  • strpos($haystack, $needle) — но array_search($needle, $haystack).

Я уж и не знаю, о каких интуитивных ожиданиях можно тут говорить.
Ой, ну как в СССР: есть «запрещено», а есть «строго запрещено». «Воспрещено» ещё. А в PHP есть E_ALL и E_ACTUALLY_ALL.

Почему-то array_search, array_map, array_filter, но ksort, krsort и так далее.

Вообще в нём реально слишком много подобного веселья, чтобы можно было говорить о каких-то интуитивных ожиданиях.
Вот этот еще доставляет:

$a = "2d9"; $a++; $a++; echo $a;

Просто жопа.

Да, тут "2d9" -> "2e0" -> 2 -> 3

Как всё же хорошо, что я не попал в эпоху PHP.
Она ещё не кончилась. Даже намеков на конец особых нет.
UFO just landed and posted this here
Полно кандидатов. Послушайте адептов той или иной религии платформы — она должна быть на месте PHP. Но на вопрос «почему?» обычно начинают сыпать техническими терминами, но важны не они, а экономические преимущества.
UFO just landed and posted this here
node.js, ruby, python — полно альтернатив
Собственно, когда читаешь инструкции к бо-о-ольшим ЭВМ, к тем, которые занимали комнаты, так вот там так интересно и настолько неинтуитивно многие назначения клавиш, многие программы сделаны, что диву даешься.

А потом подумаешь — люди делали ЭВМ не чтобы было удобно запоминать (знаешь одну команду — знаешь их все), а чтобы, выучив синтаксис, успешно пользоваться хорошим инструментом.

Так же и с PHP: придирки к форме кода совершенно обоснованы, но, признаемся, не форма или ее отсутствие сделали язык тем, что он есть.
А раньше вон вообще был натуральный обмен и всё такое. Но мы говорим о современном языке программирования с наличием достаточного количества конкурентов. Я был бы совсем не против, если бы в следующей версии PHP волевым решением унифицировали подход к сигнатурам функций и логике возвращаемых значений.
отвечал ohmytribe, да с уровнем ошибся :)

Я был бы не против, чтобы следующеую версию тогда названи PHPM, вроде как модифицированный, или еще как. А то потом гугление убивать будет, как сейчас с Joomloy какой-нибудь: гугл выдает ссылки на страницы про Джумлу, но не ориентируется в версиях ее, и отбирать потом в ссылках то, что к твоей версии относится — это мрак.

А еще лучше, чтобы в имени новой версии языка букв PHP не было бы, чтобы вообще коллизий не случилось.

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

Даже в родном мануале.
UFO just landed and posted this here
Ну, в таком случае она бы нарушилась не в чём-то, а конкретно. Но только для смены сигнатур можно запросто сделать автоматическую миграцию. Вот для возвратов уже сложнее.
За сборник спасибо огромное. Человек там от души пишет, видно!
E_ACTUALLY_ALL не существует, это выдумка хаятелей PHP.
Соль в том, что по отношению к PHP люди склонны по умолчанию верить таким вещам в силу того, что дизайн и история его развития там реально ужасны, и можно привести кучу примеров и похуже.
Из официального мануала:
// Report all PHP errors (see changelog)
error_reporting(E_ALL);

// Report all PHP errors
error_reporting(-1);



Tip
Passing in the value -1 will show every possible error, even when new levels and constants are added in future PHP versions. The E_ALL constant also behaves this way as of PHP 5.4.


E_ACTUALLY_ALL — это фиктивное распространнённое именование константы -1. Так что никаких выдумок.
фиктивное

никаких выдумок.

Завис…
Окей, единственная выдумка — название константы. Но сама парадоксальная ситуация (с фактическим наличием константы, которую можно смело называть E_ACTUALLY_ALL) так или иначе присутствует.

Так что «хаятели PHP» не такие уж лгуны, какими их пытались представить здесь.
Это проблема runtime библиотеки PHP, а не языка. Если брать сам язык, то JavaScript богат магией больше чем PHP. Если runtime библиотеку можно заменить, то фундамент языка ничем не заменишь.
Посмотрите, тут ещё Мицгол кинул ссылку на интересный «целый сборник». Там перечислено такое количество проблем именно языка PHP, какое и джаваскрипту не снилось.
не ругайтесь, оба говно, и оба потому что исторически сложилось
по ссылкам очень много ерунды вида «не осилил, потому WTF»

особенно про ruby
При работе в полноценной IDE это не вызывает проблем — сразу видны сигнатуры. В PHP, конечно, есть неочевидные вещи, но за исключением редких кейсов (на самом деле почти исключительно неперехватываемые фаталы с неочевидной причиной) язык вполне пригоден для крупного enterprise программирования — как Java, только гораздо более лаконичная и удобная в разработке.
При написании кода сигнатуры да, видны. При чтении кода никаких подсказок IDE не предлагает, а в некоторых случаях всё равно неясно, что за параметр что означает.
UFO just landed and posted this here
UFO just landed and posted this here
PHP — помойка, что уж скрывать. Да и создатель языка этого не скрывает. Ещё не видел ни одного языка с такой «играй гармонь» философией. Достаточно сравнить сигнатуры различных функций из одной и той же стандартной библиотеки.
Этот код писал человек-обфускатор. Зачем-то он умышленно запутывал код.
+ к неявностям

for (i in [1, 2, 3]) console.log(i + 1);


Что выведет?
Ну всем же ясно, что нужно писать for (i in [1, 2, 3]) console.log(+i + 1)

А так — да, один из моих любимых примеров.
for (var i in a=[1, 2, 3]) console.log(a[i] + 1)
Он же пишет «удается реализовать map таким образом, что»
Например, в Scala так просто нельзя написать. Там map принимает один аргумент.
Вот у меня гораздо большее раздражение вызывает когда в чужом коде приходится читать такое:

!~utils.indexOf()
Что не так с этим кодом? Кроме того, что его можно по разным соображениям запретить код-стайлом.
С ходу неясно его назначение. Зачем нужна побитовая инверсия результата indexOf() с последующей логической инверсией? Навскидку применение инверсии дважды дает исходный результат, но тут дважды (если не трижды) используется неявные приведения и чтобы быть уверенным в результате нужно лезть в описания и составлять таблицу истинности для различных вариантов возврата indexOf()

Вообще-то «!~x» — это такой извращённый способ записи «x != -1», потому что побитовая инверсия приводит к нулю число -1 (а все другие числа — не к нулю), а логическая инверсия нужна для того, чтобы этот ноль (в логическом смысле — ложь) сделать истинным (а все другие числа — наоборот, ложными).

Функция indexOf() запроектирована таким образом, чтобы возвращать именно -1, когда искомое не найдено. Именно этим обстоятельством порождается соблазн использовать необычную комбинацию операций «~» и «!», дающую некоторый желаемый результат только при применении к минус единице.
Так почему бы бл… дь не написать «x != -1»!!!

Извините за френч. Просто всегда удивляло то, что некоторые люди имеют неисправимую страсть все запутывать…
Ваш вариант длиннее! На целых два символа! А мы тут эффективностью занимаемся, не заметили?

Вообще подобная «забота об эффективности на спичках» когда в соседней строке успешно целый лес про… бывается очень характерна как для PHP, так и для JS.
Не страсть к запутыванию, а банальная лень. !~[1,2,3,4].indexOf(getInt()) короче и интуитивнее в написании, чем [1,2,3,4].indexOf(getInt()) == -1. При чтении, конечно, сложности, у неподготовленного человека.

Это как английские конструкции типа I'm gonna nail it, etc. Не более, чем увеличивает порог входа для понимания.
Порог вхождения в клуб любителей ребусов?
Порог становления хомячков js-рами. Болевой порог, если угодно.
Это перестает быть ребусом после первого-второго использования.
Для кого-то конструкции типа var data = c && c.data || defaultData тоже ребус, но это не значит, что они плохие.
Вам реально нужно ребусы и кроссворды составлять, а не программировать. Я бы такую конструкцию отправил обратно счастливому выдумщику. И на это есть множество причин.

Первая — в языке уже предусмотрены как минимум две человекопонятные конструкции, работающие одинаково во всех языках программирования:
var data = (c && c.data) ? c.data : defaultData;

var data;
if (c && c.data) {
    data = c.data;
} else {
    data = defaultData;
}


Вторая — врядли счастливый выдумщик может на 100% сказать, что в какой-либо из реализаций интерпретатора JavaScript подобная конструкция не вызовет избыточное присваивание data = c, а также data = c.data в случае, если c.data пустое.

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

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

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

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

Но если вы делаете сложный или уникальный продукт, требующий специальные знания, типа парсера каких-нибудь нотаций, или библиотеки/фреймворка — то странным будет пытаться кому-то угодить (в части, не касающейся API).

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

Это область очень обширная и всё знать невозможно. Не лучше ли потратить время изучение чего-то более полезного, чем чтение write-once кода?
Скорее так. Если вам важно писать понятный для среднячков популярный код, естественно вы будете писать вербозно, jshint'ить и т.д.

1. var data = (c && c.data) ? c.data : defaultData; — для кого-то это также покажется ребусом, встречал как минимум двух человек с таким отзывом об этой конструкции.

2. Closure compiler на выходе все приводит к таким конструкциям: пример. Этот аргумент имеет смысл только в девиантных случаях написания js-скриптов под фотошоп или еще какой невероятный диалект. В целом — притянуто за уши, так как работает взде и одинаково.

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

4. Не работайте?
1. Не стоит спекулировать сравнением общепринятой условной тернарной операции и строго специфичного для JavaScript возврата лоигческих операторов.

2. Согласен, это задокументрировано в стандарте. Надеюсь, вы именно от этого отталкиваетесь, а не от компилятора Closure.

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

Вы, наверное, ещё и тернарные операторы комбинируете.

4. Приходится иногда, т.к. мир, как оказалось, переполнен «профессионалами», пишущими «короткие опрятные конструкции».
1. С чего вы решили, что ваш «совет» про другие языки вообще применим? То, что в других языках это работает по-другому, не значит, что теперь надо игнорировать прекрасно задокументированные языковые конструкции. Не вижу ниодной причины js-программистам подстраивать код под персонажей, не знающих JS, но рассказывающих, как кто-то должен писать код; или под какие-либо неведомые стандарты людей не из JS. Хотите писать на универсальном языке всего — это лично ваш выбор.

2. Хороший код необязательно должен быть написан по стандартам. Неудивительно, почему вас беспокоят расходы заказчика — если каждую строчку сверять со всеми возможными интерпретаторам, да еще и со стандартом…

3. У меня другое мнение на этот счет. Посмотрите код jquery, который полон данных приемов, или raphael, и расскажите их авторам, что они — неумелые и ленивые.

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

jQuery — open source с большим количеством (50+) контрибьюторов со всем вытекающим. Доходит до смешного. Вот это вот fn.guid=fn.guid сделано ради лаконичности?
proxy.guid = fn.guid = fn.guid || jQuery.guid++;

jsperf.com/conventional-if-versus-js-specific-logic-return (примерно одинаковая ситуация при наличии fn.guid и отсутствии fn.guid)

В промышленной коммерческой разработке такого не должно быть.

А вот вам тест вашего решения и тернарной операции:
jsperf.com/ternary-versus-dmitriy-f-solution (для ситуации, когда c.data определён, скорость вашего решения уступает на 35%)

Добро пожаловать в мир, где профессионалов определяют не по специфичности используемых конструкций, а по пониманию, зачем они их используют или не используют.
Спасибо за тест, правда сравнительное быстродействие логических операторов мне известно уже давно. В данных тестах, кстати, у меня показатель 5%, а не 35%. Правда такие тесты несут мало прикладной пользы.
Во-первых, в клиентских (браузерных) приложениях узким местом в быстродействии, как правило, является либо неправильная работа с DOM/стилями (лишние reflows), либо косяки в построении алгоритмов (типа описанного в статье). Мелочи вроде циклов, логических операторов, скорости парсинга и т.д. и т. п. имеют на 3-6 порядков более высокие показатели, и на их быстродействие можно смело закрыть глаза.
Во-вторых, если вы используете компилятор, как это следовало бы делать, (а может даже компилируемый язык типа CoffeeScript, как не следовало бы делать?), то вы можете смело игнорировать быстродействие отдельных команд, так как оптимизацию этого всего компилятор берет на себя.

Поэтому вопрос остается в удобстве чтения/написания кода, а также в оптимизации алгоритмов. Я считаю, что если создатель библиотеки придерживается определенных конвенций в коде, гарантирует корректное функционирование API и поддерживает продукт, то претензии к заумности кода к нему неуместны.
(для ситуации, когда c.data определён, скорость вашего решения уступает на 35%)

jsperf.com/ternary-versus-dmitry-f-data-exists

Как мы видели, тернарный оператор:
1. Занимает не намного больше символов (вы же не предлагаете data именовать d, а defaultData — dd, значит, с десятком-другим лишних символов вы готовы смириться);
2. Понятен абсолютному большинству программистов большинства популярных языков;
3. Намного проще читается за счёт наличия ?: которые строго отделяют условие и присваиваемое значение (в отличие от возвратов логических операций, в которых условия замиксованы с присваиваемыми значениями).
4. Быстрее.
5. Более гибок (хотя менее гибок, чем if-else).

Насчёт удобства — первый раз вижу человека, который приводит jQuery (про raphael даже не говорю) как пример удобочитаемого кода.

В целом я понял вашу позицию. Тесты проводил с целью понять, может быть, я и правда заблуждаюсь и для перехода на такие конструкции и правда есть какие-то причины. Но нет, остаюсь на традиционных.
Приведите тогда ваши примеры удобочитаемого кода известных продуктов — посмотреть, сравнить.
Лично я не вижу ничего смешного в конструкции:
proxy.guid = fn.guid = fn.guid || jQuery.guid++;

Посмотрите на гитхабе код хотя бы топ-50 проектов на JS, везде есть специфичные для JS конструкции. Да к примеру connect, express, которые используются во многих хайлоад проектах. Или Kraken @ PayPal (который переводит все свои фронты с jvm на node.js).

Ваши тесты — это сродне убрать соломинку из стога сена (в ущерб лаконичности) на фоне разницы минимум на порядок между ES5 Array[[prototype]] ф-ями forEach, map, reduce etc. и простым лупом. И что теперь, не использовать их?

Я сначала хотел спросить, чего ради вы ратуете за отказ от сахара, но потом увидел
2. Понятен абсолютному большинству программистов большинства популярных языков;
Может тогда питонистам отказаться от лямбд? А что делать тем, кто пишет на Clojure? :)

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

Дело хозяйское.

Ваши тесты — это сродне убрать соломинку из стога сена (в ущерб лаконичности) на фоне разницы минимум на порядок между ES5 Array[[prototype]] ф-ями forEach, map, reduce etc. и простым лупом. И что теперь, не использовать их?

Использовать, но думая.

Может тогда питонистам отказаться от лямбд?

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

А что, использование !~ и специфического возврата в JavaScript — это конвенция? Где можно об этом прочитать? Пока что я, тем не менее, вижу всего лишь использование специфической неоптимальной конструкции в угоду субъективной лаконичности.

Посмотрите на гитхабе код хотя бы топ-50 проектов на JS

Вероятно, но тогда придётся тестировать каждое такое решение, а я слегка устал этим заниматься. Кроме того, я вижу и использование в этих же топ-50 стандартных условных конструкций для тех же целей, что наталкивает на мысль, что никакой конвенции нет.
Вот именно, что извращенный. Чтобы понять смысл такого нужно разобрать с конца все операции, понять, что смысл !~x это x != -1 и убедиться, что это всегда так, без исключений когда x например undefined или null.
!~undefined
>false
undefined === -1
>false
!~null
>false
null === -1
>false
UFO just landed and posted this here
Зачем? Показать как хорошо знаешь все нюансы?
UFO just landed and posted this here
В том-то и дело, что она только короткая.
UFO just landed and posted this here
"!~" — вот это опрятная конструкция? В брейнфак языках, вероятно?
«!~x» равносильно «x == -1», а не «x != -1». В статье и у вас ошибка.
Да, правда. У меня ошибка :-(
Ну лично я здесь вижу вызов операции not к значению, возвращённому деструктором (который, вообще-то, void)
Мне, как человеку, пробегавшему мимо и попробовавшему свои силы в js на единственном проекте — код выше смысловой нагрузки не несёт.
Гораздо больше бесят люди, которые прекрасно знают, что и зачем, но продолжают с пеной у рта орать " ПОЧЕМУ!? ЗАЧЕМ!?"
Более-менее очередное «Я пришел из языка А в язык Б, и меня бесит, что тут все не так!», но несколько здравых мыслей есть.
Аццкое вуду-метапрограммирование действительно бывает тяжело разобрать. Stateful-объекты тоже могут причинять неприятности. Ну, а за волшебные операторы приведения типа (унарный +, ~ и т.п.) вообще надо избивать клавиатурой, имхо.
Инструменты контроля качества, вроде jshint, и избивают.
Думаю, все библиотеки плюс-минус имеют тормоза, причем достаточно ощутимые. Например, недавно отлаживал тормоза на странице, js отрабатывал за полсекунды. С помощью профайлера установил, что datepicker создается за 200мс (!). Как такое возможно выпускать? С использованием того же Angular удалось создать свой велосипед, который работает за 50мс, что уже приемлемо, учитывая, что пользователь субъективно ощущает задержки более 100мс.
Мое мнение: некоторые автора библиотек выпускают их для галочки не заботясь ни о памяти, ни о скорости. Но есть и «нормальные» библиотеки.
Поделитесь Велосипедом.
Печкин.
Он заточен под мое приложение, плюс нужен boostrap последний версии, и еще обернуто в requirejs, но в принципе может что-то и пригодится. Плюс я его еще не довел до ума.
Зато позволяет делать multiselect произвольных дат, можно выбирать диапазоны дат и работает на мобильном.
Код pastebin.com/6NBG0T2N
Пример stage.frimio.com/#/event/new/date (в консоли есть время создания, «new/date end — start»; учтите, что там разница 100мс потому что кроме datepicker в это время еще один скрипт рендерится за 40 мс).
UFO just landed and posted this here
Не всё так плохо. Всегда помните, что плохой программист создаёт два новых рабочих места в год.
UFO just landed and posted this here
Как-правило, такие мысли возникают после непродолжительного знакомства с языком, и со временем весь этот батхерт проходит.
А критиковать можно абсолютно любой язык.
JS на мой взгляд достаточно сложный язык: язык динамический и очень гибкий в плане организации кода, все это создает массу соблазнов писать в одном месте «так», в другом «иначе» (так как скопировал код откуда-нибудь) и все это обильно снабжать все возможными сайд-эффектами (начиная от перфоманса и заканчивая крашем скриптов), в догонку все это множится взрывной популярностью JS с большим количеством новичков-программистов, которые на js писали простенькие скрипты в веб-студии для html-страничек, а сейчас взялись и за фреймворки и либы. Мне кажется, чтобы управляться с JS на уровне грамотного написания библиотек нужен неплохой опыт в классических типизированных языках, которые приучают к порядку в коде и определенной дисциплине.
А что вы хотели от языка, который придумал Дилберт? (Погуглите фотку создателя.)

Пройдёт мода на веб-спа-приложения, все эти фреймворки канут в лету, как сейчас от них не остаётся ничего кроме пустой страницы, если отключить JavaScript в браузере.
Главный недостаток динамической типизации — прятание ошибок. Вместо того, чтобы выявить ошибку прямо в точке возникновения, мы заметаем её под ковёр.
Там, где жёстко типизированный код посыплется (а то и вообще не скомпилируется), динамический проглотит ошибку и будет выполняться дальше как ни в чём не бывало. А упадёт когда-нибудь потом. Или вообще не упадёт, а просто не выполнит часть действий. И будем потом сутками искать с отладчиком, почему так происходит и где всё же ошибка.
А чтобы было совсем уж нескучно, библиотеки JS битком набиты коллбэчной лапшой. Счастливой отладки, да…
Противники статической типизации предлагают разработчикам писать тесты. И тогда всё вроде как будет хорошо. На деле же найти литературу, где внятно описывается, как создавать эти самые тесты, очень сложно.
Типичный набор тестов, написанный простым смертным (а не гуру тестирования) помогает всего лишь выявить ошибку, но совершенно не помогает определить точку её возникновения. Представьте, что в проекте есть модуль C, который использует модуль B, который использует модуль A. И вот где-то в модуле A есть ошибка. Что произойдёт? Правильно, сразу же отвалятся пакеты тестов A, B, C.
Именно так в реальности и происходит: любая ошибка приводит к тому, что отваливается сразу половина всех имеющихся тестов, и где же теперь искать ошибку, в какой части проекта, остаётся по-прежнему непонятно.
Ну да, порог вхождения в профессиональную разработку приложений на JS (как и в любом языке) довольно высокий.

А чтобы было совсем уж нескучно, библиотеки JS битком набиты коллбэчной лапшой

Как наличие коллбеков существенно влияет отладку?
В конце-концов есть Deferred.

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

Чем чтение документации не устраивает?
С трудом представляю разработчика который бы запутался в том же qunitjs

Типичный набор тестов, написанный простым смертным (а не гуру тестирования) помогает всего лишь выявить ошибку, но совершенно не помогает определить точку её возникновения.

Инструментов и техник для выявления ошибок на этапе тестирования и времени выполнения сейчас предостаточно: линтеры, дебаггеры, автотесты, логгеры (да, нужно заранее позаботиться о стеке вызовов), статические анализаторы (например Goggle Closure Compliler)?

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

> Противники статической типизации предлагают разработчикам писать тесты.

Как будто в языках со статической типизацией писать тесты не нужно.
В языках со статической типизацией весь код автоматически как бы покрыт кучей тупых, простых, но неотключаемых тестов. О несоответствии типов вы узнаёте не то что до запуска программы, а и до того, как она до конца написана (в приличных IDE).

Такого плотного покрытия тестов никто и для какого языка никогда руками не делает.
Тут дело не в динамике/статике, а в строгости/нестрогости типизации.
Ха чёрточка ха чёрточка ха. Это вообще другой параметр. Строгая типизация влияет на то, как хорошо типизация отлавливает [потенциальные] ошибки. Разница же между статической и динамической типизацией определяет когда именно отлавливаются [потенциальные] ошибки. Если вы учтёте что подавляющее большинство ошибок обнаруживается в обработчиках ошибок (которые, во-первых, сложнее тестировать, а во-вторых сложнее себе представить в какой обстановке они работают), то вы поймёте почему я считаю статичность типизации важнее уровня её строгости.

Писать что-то на динамически типизированных языках можно либо что-то «для себя» (когда вы помните где что какого типа), либо от безысходности. Для web'а характерен второй случай — почему, собственно, и случается «плач Ярославны» подобный данной статье.

P.S. Ещё кое-как удаётся приспособить к делу динамические языки в инструментах для разработки, где вы можете себе позволить один-единственный «обработчик ошибок» — «что-то пошло не так? прекратить вообще всё к чёртовой матери, вывалиться в систему, пусть разрабочик теперь всё чинит/чистит». Для пользовательских приложений этот вариант не подходит.
Окей, тогда требуйте строгой статической типизации. Нестрогая статическая это в данном контексте не меньшее зло.

Хотя приличная IDE на проблемы укажет и со строгой динамикой.
Приличная IDE укажет на многие проблемы и с нестрогой динамикой, типа задали переменную строкой, а пытаемся как с числом обращаться.
В языке с нестрогой динамикой такая операция наверняка будет совершенно легальной, так что ругаться не на что.
Приличная IDE выдаст предупреждение по типу «тут вы строку пытаетесь умножить», которое можно игнорировать или вообще отключить.
Если типизация статическая, но слабая, то никаких ошибок вы не увидите. Так же будут неявные преобразования одного типа к другому и т. п. Ну вот представьте, что JS стал языком со статической типизацией, для всех переменных нужно задавать тип, но если вы напишите что-то типа int i = «1234», то получите не ошибку, а 224197226800 или 1234.
Нестрогая типизация не означает, что всё превращается во всё, она просто обозначает, что какие-то преобразования типов могут делаться неявно.

Типичным примером языков со статической нестрогой типизацией является C++ — и представьте себе, она там отлично срабатывает и вполне себе отлавливает кучу ошибок.

Проблема JS (и PHP, кстати), не в том, что у них типизация нестрогая, а в том, что она черезмерно нестрогая. В некоторых библиотеках C++ с этим тоже перебарщивают, конечно, но в целом там есть только парочка моментов, когда нестрогость мешает (например неявное преобразование float'ов и int'ов иногда сильно мешает).
Чрезмерно или нет это уже субъективные оценки. Есть строгая типизация (Python и Java вроде из мэйнстрима), есть её отсутствие (Ассемблер и, вроде, С) и есть нестрогая, сильная и слабая — на любителя.
Вы не путаете статическую типизацию с сильной или строгой? А компиляция вообще не причем.

Ну и про тесты: просто нужно (для локализации ошибок) писать юнит-тесты, которые тестируют модули отдельно, а не интеграционные, которые тестируют связку модулей.
Полностью согласен с автором. Без матов ситуацию с библиотеками на JS не опишешь. Только среди библиотек на JS логотип (логотип! у бибилотеки!) и HTML5-сайт с названием оной библиотеки в 100500 кегле на весь первый экран бывает чаще, чем хороший код и документация.
twitter.github.io/typeahead.js/:
a fast and fully-featured autocomplete library

Вот просто интересно, что заставило бы авторов не включать эпитет «fast» в описание своей библиотеки, с учетом того, что рассказано в статье.
То-то в последнее время пестрел твитами аккаунт Армина Ронахера по поводу typeahead, и проклинания того, кто этот код писал. Я долгое время не понимал — почему Армин писал свой «бинарный поиск» для строк. Теперь, после статьи, проблема более мне понятно, хоть на js и не пишу.
Всегда забавляли операторы ===, !== в javascript.Почему уж тогда нету ==== и =====, где каждый символ добавляет свою уникальную особенность оператору. А ещё можно комбинировать, например =!=.

Язык точно странный. Но он очень уж кроссплатформенный.
Автору следовало бы понять ещё на этапе выбора компонента, что для поиска совпадений по 26К городам надо писать свой поиск, а не пользоваться компонентом, предназначенным для общих случаев, поиска по небольшим массивам (тэги и т.п.).

И поиск этот можно сделать как очень быстрым (поиск по 30К+ городам ~1—3 мс), так и не блокирующим UI (async или webworkers).

Все дальнейшие выводы автора о языке и других компонентах основываются на не соответствии его ожиданий (очевидно, полагаясь на опыт в других ЯП), документации (которую довольно часто не читают) и особенностях (WAT?) JS.
А что мешает компоненту для общих случаев быть быстрым? Разницы-то в функционале никакой.
Потому что, допустим, для поиска на клиенте по массиву в 30К+ городов есть смысл перенести поиск в воркер или использовать одно{двух}уровневый хэш или поиск чанками асинхронно (чтобы не блокировать UI) как фоллбэки. И это и есть своё кастомное решение, а для автокомплита по 100 тегам достаточно и

return list.filter(function (item) { return item.indexOf(query) != -1; });
UFO just landed and posted this here
Еще распространенная ошибка при live поиске — это
отсутствие учета вводимых символов с клавиатуры.

1.Например первые (100-200)мс можно ждать второго символа,
и только если его нет запускать поиск.
2. Обязательно прерывать текущий поиск если введен новый символ
в патерн поиска.

Реализация этих 2-х рекомендаций заставила интерфейс работать в 2 раза быстрее.
Это конечно хороший паттерн (в определённых случаях), но он мало что изменит, если алгоритм неоптимальный (как в случае автора).
Sign up to leave a comment.

Articles

Change theme settings