Пользователь
8,0
рейтинг
11 декабря 2013 в 14:21

Разработка → Хватит быть милым и умным из песочницы

Этот текст является переводом статьи 'Stop Being Cute and Clever' небезызвестного (по крайней мере, в Python-комьюнити) Армина Ронахера.

Последние дни в свободное время я занимался созданием планировщика. Идея была простой: создать некий клон worldtime buddy c использованием AngularJS и некоторых других JavaScript-библиотек.

И знаете что? Это было отнюдь не весело. Я уже давно так сильно не злился, работая над чем-либо, а это что-то значит, потому что обычно я быстро высказываю своё недовольство (прошу прощения у моих фолловеров в Twitter).

Я регулярно использую JavaScript, но мне редко приходилось сталкиваться с кодом других людей. Обычно я привязан только к jQuery, underscore и иногда AngularJS. Однако в этот раз я пошел ва-банк и решил использовать различные сторонние библиотеки.

Для данного проекта я использовал jQuery, без которого уже нельзя обойтись (да и зачем?), и AngularJS с некоторыми UI-компонентами (angular-ui и биндинги к jQuery UI). Для работы с часовыми поясами использовался moment.js.

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

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

У меня было много проблем с js-библиотеками, и все они являлись результатом того, что всем, похоже, наплевать на особенности работы языка.

Причина, по которой я стал активно изучать сторонний JavaScript-код, была в том, что моя наивная попытка отправки 3mb названий городов в библиотеку автодополнения typeahead.js привела к невероятно тормозному UI. Очевидно, что сейчас ни один умный человек не будет отправлять сразу так много данных в поле с автодополнением, а сначала отфильтрует их на стороне сервера. Но данная проблема кроется не в медленной загрузке данных, а как раз в медленной фильтрации. Чего я никак не мог понять, ведь даже если происходит линейный поиск по 26 000 элементов, он не должен быть настолько медленным.

Предыстория


Итак, интерфейс тормозил — очевидно, ошибка была в моей попытке передать слишком большое количество данных. Но интересно, что производительность падала именно при использовании typeahead-виджета. Причем иногда весьма своеобразным образом. Чтобы показать, какое сумасшествие происходило, я приведу несколько начальных тестов:

  1. Ищем San Francisco, печатая «san». ~200ms.
  2. Ищем San Francisco, печатая «fran». ~200ms.
  3. Ищем San Francisco, печатая «san fran». Секунда.
  4. Ищем San Francisco, снова печатая «san». Секунда.

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

Первое, что я сделал – это использовал новый профайлер Firefox’a, чтобы увидеть, на что тратится так много времени. И очень быстро нашел в typeahead кучу вещей, которые были слишком странными.

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

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

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

Основываясь на этом, я теперь убеждён, что JS представляет из себя своеобразный Дикий Запад разработки софта. В первую очередь потому что он конкурирует с кодом PHP года 2003-го в плане качества, но судя по всему это волнует меньшее количество людей, так как он работает на клиентской стороне, а не на сервере. Вы не должны платить за медленно работающий JavaScript.

«Умный» код


Первая болевая точка – люди, которым JS кажется милым и 'умным' языком. И это делает меня до смешного параноидальным при проведении ревью кода и поиске багов. Даже если вы знаете примененные идиомы, вы не можете быть уверены, будут ли побочные эффекты намеренными, или кто-то просто сделал ошибку.

Для примера я приведу кусок typeahead.js:
_transformDatum: function(datum) {
    var value = utils.isString(datum) ? datum : datum[this.valueKey],
        tokens = datum.tokens || utils.tokenizeText(value), 
        item = {
            value: value,
            tokens: tokens
        };
    if (utils.isString(datum)) {
        item.datum = {};
        item.datum[this.valueKey] = datum;
    } else {
        item.datum = datum;
    }
    item.tokens = utils.filter(item.tokens, function(token) {
        return !utils.isBlankString(token);
    });
    item.tokens = utils.map(item.tokens, function(token) {
        return token.toLowerCase();
    });
    return item;
}

Это всего лишь одна функция, которая тем не менее зацепила меня по многим причинам. Всё, что делает функция – конвертирует объект c данными в элемент списка. Что представляет из себя объект с данными? Где-то здесь и начинается интересное. Похоже, что автор библиотеки в какой-то момент пересмотрел свой подход. Всё должно было начинаться с приёма функцией строки и дальнейшего оборачивания её объектом с value-атрибутом (тоже строкой) и массивом токенов. Однако теперь возвращаемый объект – обёртка над объектом данных (или строкой) с совершенно иным интерфейсом. Копируется куча данных, и затем просто переименовываются некоторые атрибуты.

Предположим, что что на вход поступает объект следующего вида:
{
    "value": "San Francisco",
    "tokens": ["san", "francisco"],
    "extra": {}
}

Тогда он трансформируется в такой:
{
    "value": "San Francisco",
    "tokens": ["san", "francisco"],
    "datum": {
        "value": "San Francisco",
        "tokens": ["san", "francisco"],
        "extra": {}
    }
}

Я могу понять, почему код заканчивает работу именно так, но глядя на совершенно иной участок кода, совершенно неочевидно, почему мой datum-объект стал другим объектом, тем не менее содержащим те же данные. Даже хуже: удваивается используемая объектом память, потому что при операциях с массивами копируются токены. Получается, что я мог просто отправить объекты данных в правильном формате, сократив при этом потребление памяти на 10MB.

А ведь такой код достаточно типичен для JavaScript, и это расстраивает. Он неясен, он странный, ему не хватает информации о типах. И он слишком 'умный'.

Он просто оперирует объектами. Ты не можешь спросить у объекта: datum, в нужном ли ты формате? Это просто объект. При копании в деталях реализации оказалось, что можно отправить целую кучу различных типов данных на вход – и всё бы продолжало работать, просто делая что-то другое по началу и ломаясь намного позже. Впечатляет количество неверной информации, которую JS может обработать, выдав каким-то образом результат.

Мало того, что не хватает типизации, так этот код еще легкомысленно злоупотребляет операторами и функциональным программированием. Не передать словами, насколько недоверчиво я отношусь к такому стилю написания JS-кода, учитывая то, как странно работает функция map. Не многим языкам удается реализовать map таким образом, что ["1", "2", "3"].map(parseInt) выльется в [1, NaN, NaN].

Злоупотребление операторами широко распространено. Немного ниже можно увидеть замечательный кусок кода:
_processData: function(data) {
    var that = this, itemHash = {}, adjacencyList = {};
    utils.each(data, function(i, datum) {
        var item = that._transformDatum(datum), id = utils.getUniqueId(item.value);
        itemHash[id] = item;
        utils.each(item.tokens, function(i, token) {
            var character = token.charAt(0), adjacency =
                adjacencyList[character] || (adjacencyList[character] = [ id ]);
            !~utils.indexOf(adjacency, id) && adjacency.push(id);
        });
    });
    return {
        itemHash: itemHash,
        adjacencyList: adjacencyList
    };
}

Для информации: utils.indexOf – простой линейный поиск в массиве, а utils.getUniqueId возвращает постоянно увеличивающееся целое число в качестве идентификатора.

Очевидно, автор этого кода знал о хэш-таблицах со сложностью O(1), иначе он не положил бы эту строку в hashmap. Но всё же несколькими строками ниже происходит линейный поиск перед позиционированием элемента в списке. Если закинуть в этот код 100 000 токенов, он будет работать очень медленно, поверьте.

Также хочется обратить внимание на это цикл:
utils.each(item.tokens, function(i, token) {
    var character = token.charAt(0), adjacency =
        adjacencyList[character] || (adjacencyList[character] = [ id ]);
    !~utils.indexOf(adjacency, id) && adjacency.push(id);
});

Я просто уверен, что автор был очень горд. Для начала, почему именно так? Разве !~utils.indexOf(...) && действительно достойная замена if (utils.indexOf(...) >= 0)? Не говоря уже о том, что hashmap со списками смежности называется adjacencyList… Или то, что список инициализируется ID строки, и потом сразу же проходит линейный поиск по всему списку для поиска этого же элемента еще раз. Или что значение в хэш-таблицу вносится по булевой проверке списка и с использованием оператора ‘или’ для выполнения присваивания.

Еще один распространенный хак – использование унарного оператора + (который в других языках бесполезен, так как это noop) для перевода строки в число. +value – то же самое, что parseInt(value, 10).

У меня есть теория, что всё это операторное безумие пошло из Ruby. Но в Ruby это имеет смысл, так как там только два объекта со значением 'ложь': false и nil. Всё остальное – 'истина'. Весь язык базируется на этой концепции. В JS же многие объекты ложны. А затем иногда – нет.

Например, экземпляр пустой строки "" приравнивается к false. За исключением того случая, когда это объект. А строки иногда становятся объектами по случайности. Функция jQuery each передает текущее значение итератора как this. Но, так как this не может ссылаться на примитивные типы, объект передается как строка, обернутая объектом.

Так что в некоторых ситуациях поведение может сильно отличаться:
> !'';
true

> !new String('');
false

> '' == new String('');
true


Симпатизировать операторам можно в Ruby, но никак не в JavaScript. Это банально опасно. Не то что бы я не доверяю человеку, который протестировал свой код и знает, что он делает. Просто если кто-то другой посмотрит потом на этот код, ему будет неясно, запланировано ли такое поведение разработчиком.

Использование ~ для проверки возвращаемого функцией indexOf значения, которое может быть равным -1 при отсутствии элемента, просто неразумно. И пожалуйста, не говорите мне, что «так же быстрее».

Работаем «наживую»


Сомнительное использование операторов это одно, но действительно убивает то, что динамическую природу JS возводят в абсолют. Как по мне, даже Python – избыточно динамический язык, но питонисты хотя бы довольно разумно сводят модификации классов и пространств имён в runtime к минимуму. Но в мире JavaScript всё по другому. И особенно в мире AngularJS.

Классы не существуют, в JS используются объекты, которые иногда могут иметь прототипы. Хотя обычно все просто помещают функции в объекты. А иногда и функции в функции. Странное клонирование объектов тоже нормальное явление, ну разве что не в случае, когда состояние объекта часто меняется.

Может показаться, что директивы в Angular неплохи, до тех пор, пока вам не встретится директива, делающая почти то, что вам нужно. В большинстве случаев директива монолитна, и единственный способ изменить её – это добавить еще одну директиву с большим приоритетом, которая пропатчит предыдущую. Я бы не расстроился, если бы наследование классов ушло в прошлое, уступив место совмещению, но такой 'обезьяний патчинг' – не мой стиль.

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

Например, Angular использует систему слежения за изменениями моделей и DOM для автоматической их синхронизации. Мало того, что это чертовски медленно, так народ еще и придумывает всякие обходные пути для предотвращения firing’а обработчиков. Такая логика быстро становится до смешного запутанной.

Неизменяемость


Чем выше уровень языка программирование, тем более неизменяемыми становятся вещи. Но только не в JavaScript. API становятся всё больше засоренными stateful-концепциями. Может быть, неуместно жаловаться на это в плане производительности, но это начинает очень быстро раздражать. Одни из самых неприятных багов в моём планировщике были в изменяемой природе moment-объектов. Вместо того, чтобы вернуть новый объект, foo.add('minutes', 1) изменял исходный. Нет, я знал про это, в документации к API всё описано. Но, к сожалению, случайно передал туда ссылку, и она была изменена.

Правда, в теории JS должен быть отличным инструментом для построения API, использующего неизменяемые объекты при условии возможности их ‘заморозки’ по желанию. Это как раз то, чего не хватает Python. Однако в тоже время Python предоставляет больше инструментов, делающих immutable-объекты более интересными. Например, поддержку перегрузки операторов, и first-классы, которые позволяют использовать такие объекты в качестве ключей хэш-таблиц.

«Полезная магия»


Я люблю Angular, очень. Это одна из разумнейших систем для проектирования UI в JavaScript, но присутствие в ней магии пугает. Начинается всё с простых вещей. Например, библиотека переименовывает директивы. Если вы создадите директиву fooBar, в DOM она попадёт как foo-bar. Почему? Предположим, для однообразия с style DOM API, в котором делалось нечто похожее ранее. Но это делает код запутанным, поскольку вы можете не знать в точности, как точно называется директива. Также всё это полностью игнорирует идею пространств имён. Если у вас есть две директивы с одинаковыми именами в разных Angular-приложениях, они будут конфликтовать.

Внедрение зависимости в Angular происходит по умолчанию через конвертацию JS-функции в строку и последующее использование регулярного выражения для разбора аргументов. Если вы новичок в AnguarJS, для вас это вообще не будет иметь никакого смысла, а мне даже сейчас эта идея кажется плохой. Это конфликтует с тем, что люди делали в течении долгого времени в JS: локальные переменные рассматриваются как анонимные. Имя ни на что не влияет. Именно этим минимизаторы пользовались целую вечность. Однако всё же это не в полной мере относится к Angular, так как есть альтернативная возможность явного объявления зависимостей.

Что за слои?


Одним из неудобств после перехода с Python на клиентский JavaScript было отсутствие абстракций. В качестве примера, Angular позволяет получить доступ к параметрам текущего URL в виде словаря. Что он не позволяет, так это разобрать произвольную строку запроса. Почему? Потому что внутренняя функция парсинга спрятана под многими слоями замыканий, и кто-то просто не подумал, что она может быть полезной.

И такое происходит не только в Angular. JS сам по себе не имеет функции для экранирования HTML. Но DOM, очевидно, нуждается в такой функциональности в некоторых случаях. Из-за чего некоторые на полном серьёзе экранируют HTML так:
function escapeHTML(string) {
    var el = document.createElement('span');
    el.appendChild(document.createTextNode(string));
    return el.innerHTML;
}

А так можно распарсить URL:
function getQueryString(url) {
    var el = document.createElement('a');
    el.href = url;
    return el.search;
}

Это безумие, но оно везде.

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

«Но оно же работает»


PHP настолько популярен, потому что он просто работает, и не требует много времени на обучение. Целое поколение разработчиков начало работать с ним. И этой группе людей пришлось открывать болезненными способами кучу вещей, основанных на опыте предыдущих лет. Сформировался некий групповой менталитет: когда один человек копировал код другого, он особо не задумывался, как он работает. Я помню время, когда система плагинов была бредом сумасшедшего, и основным путём расширения PHP-приложений были mod-файлы. Какой-то заблуждавшийся дурак начал всё это, и все стали так делать. Я почти уверен, что именно так появились register_globals, странное ручное экранирование SQL, да и вся концепция обработки входных данных вместо нормального экранирования.

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

Даже хуже: так как всё работает в песочнице на компьютерах пользователей, никто даже не задумывается о безопасности. И в отличие от PHP производительность не имеет значения, поскольку клиентский JS «масштабируется линейно» с ростом числа пользователей, запустивших приложение.

Будущее?


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

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

Я просто надеюсь, что JavaScript-комьюнити понадобится меньше времени на подстройку, чем предшественникам.

Armin Ronacher,
09/12/2013
Egor Komissarov @komissarex
карма
50,0
рейтинг 8,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (176)

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

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

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

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

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

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

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

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

        Вывод: ваш тезис неверен.
      • 0
        Oh geez, here we go again…
    • +3
      Автор и не говорит про весь язык, он говорит про тенденции. Приходится копаться в коде различных библиотек, и действительно часто встречаешься со странными и вредными решениями. Из последнего — datatables.js, который при расширении дефолтных настроех с помощью jquery.extend, за-компанию пару раз клонирует весь массив входящих данных.
      • +2
        Авторам этаких решений надо бы откусить голову по самую жопу (да простит меня Ведьма Шарлотта) или, по крайней мере, посылать им такие запросы на слияние (pull requests), которые приводили бы их код к более приемлемому поведению.
        • +1
          Воистину так!
  • +2
    Зачем автор докопался до
    ["1", "2", "3"].map(parseInt)
    
    ? Если бы он внимательно прочитал описание обеих функций (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map и developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt) вопросов бы не возникало.
    • +13
      Кстати, да. Совершенно верно.

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

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

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

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

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

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

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

        ?
        • +15
          Достаточно
          ["1", "2", "3"].map(function(item){ return parseInt(item); })
          

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

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

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

                      А ещё у меня (в том комменте, на который был этот ответ без указания системы счисления) оказалось целых два неиспользуемых аргумента в функции. А тут оптимизация же. (Интересно, есть ли хоть какая-то реальная разница, кроме +10 символов кода.)
                      • 0
                        Вероятно расходы как на создание новых переменных. Да и современные минификаторы, скорее всего, их удалят как неиспользуемый код.
                • +1
                  по стандарту 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.

                  Как видите, ничего в стандарте про восьмеричную систему, только про шестнадцатиричную.
        • 0
          Зачем так сложно?

          ['1', '2', '3'].map(Number);
      • +12
        Это замечательная функция, наверняка полезная в хозяйстве. Но как называть того, кто придумал дать ей имя map?
        • +2
          Какое имя вы хотите предложить, как более уместное?
          • +6
            Enumerate with metadata.
        • –8
          Наверное она так названа потому что производит операцию под названием «map»? en.wikipedia.org/wiki/Map_%28higher-order_function%29
          • +17
            Как раз проблема в том, что это не каноничная функциональная map, и такое название лишь вводит в заблуждение.
      • +1
        Кстати, единичная система счисления существует: _, 1, 11, 111, 1111,…
        • 0
          Она радикально отличается от всех «нормальных» систем счисления: например, в ней непредставим нуль.
          • +2
            Ну почему же? За нуль можно принять 1. За единицу 11, за двойку 111. Только что с таким представлением вычитать в столбик не удобно, и умножать составлением матрицы, но это обходится простым правилом — при умножении рисовать на одну строку и колонку меньше, при сложении убирать одну единичку, а при вычитании — одну добавлять, про деление можете додумать.
            • 0
              А что делать с отрицательными числами? У вас выходит, что «положительный нуль» и «отрицательный нуль» — разные числа?
              А что делать с нецелыми числами?

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

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

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

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

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

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

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

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

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

                          • +8
                            Просто жопа.

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

                            Как всё же хорошо, что я не попал в эпоху PHP.
                            • +4
                              Она ещё не кончилась. Даже намеков на конец особых нет.
                              • +1
                                Есть. Просто пока не понятно, что должно быть вместо него, поэтому он пока и на месте.
                                • 0
                                  Полно кандидатов. Послушайте адептов той или иной религии платформы — она должна быть на месте PHP. Но на вопрос «почему?» обычно начинают сыпать техническими терминами, но важны не они, а экономические преимущества.
                                  • 0
                                    Дело не в этом. Адепты пусть делают то, что им положено — говорят. Здесь должен сказать свое слово корпоративный сектор. Я имею в виду, так сказать, «маленькие проектики», для которых юзать Java, C# etc не досуг. Ну, знаете, как раньше было — Вова знает PHP, давайте Вова нам напишет какую-нибудь шняжку, а эта шняжка потом разрасталась до нереальных размеров. Вот, что должно прийти на смену PHP в этой области… большой вопрос.
                                    • 0
                                      node.js, ruby, python — полно альтернатив
                        • +3
                          Собственно, когда читаешь инструкции к бо-о-ольшим ЭВМ, к тем, которые занимали комнаты, так вот там так интересно и настолько неинтуитивно многие назначения клавиш, многие программы сделаны, что диву даешься.

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

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

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

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

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

                                  Даже в родном мануале.
                            • 0
                              Искренне поддерживаю… и пускай бы, даже, обратная совместимость в чем-то нарушилась.
                              • 0
                                Ну, в таком случае она бы нарушилась не в чём-то, а конкретно. Но только для смены сигнатур можно запросто сделать автоматическую миграцию. Вот для возвратов уже сложнее.
                        • 0
                          За сборник спасибо огромное. Человек там от души пишет, видно!
                      • +2
                        E_ACTUALLY_ALL не существует, это выдумка хаятелей PHP.
                        • +1
                          Соль в том, что по отношению к PHP люди склонны по умолчанию верить таким вещам в силу того, что дизайн и история его развития там реально ужасны, и можно привести кучу примеров и похуже.
                        • 0
                          Из официального мануала:
                          // 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. Так что никаких выдумок.
                          • 0
                            фиктивное

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

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

                              Так что «хаятели PHP» не такие уж лгуны, какими их пытались представить здесь.
                    • +1
                      Это проблема runtime библиотеки PHP, а не языка. Если брать сам язык, то JavaScript богат магией больше чем PHP. Если runtime библиотеку можно заменить, то фундамент языка ничем не заменишь.
                      • +1
                        Посмотрите, тут ещё Мицгол кинул ссылку на интересный «целый сборник». Там перечислено такое количество проблем именно языка PHP, какое и джаваскрипту не снилось.
                        • +6
                          Ну и для JS есть такой же сайт: wtfjs.com/
                        • +4
                          не ругайтесь, оба говно, и оба потому что исторически сложилось
                          • 0
                            Вот еще магии из Ruby и PHP:

                            rubywtf.com/
                            www.phpwtf.org/

                            • 0
                              по ссылкам очень много ерунды вида «не осилил, потому WTF»

                              особенно про ruby
                    • +1
                      При работе в полноценной IDE это не вызывает проблем — сразу видны сигнатуры. В PHP, конечно, есть неочевидные вещи, но за исключением редких кейсов (на самом деле почти исключительно неперехватываемые фаталы с неочевидной причиной) язык вполне пригоден для крупного enterprise программирования — как Java, только гораздо более лаконичная и удобная в разработке.
                      • +1
                        При написании кода сигнатуры да, видны. При чтении кода никаких подсказок IDE не предлагает, а в некоторых случаях всё равно неясно, что за параметр что означает.
                        • 0
                          Это, к сожалению, не относится только к php. Если исходный код программы неясен, значит либо читатель этого исходного кода некомпетентен, либо он написан отвратительно. Естественно, что-то типа такого не будет понятно:
                          str_replace($a, $b, $c);

                          Недостатков php никто не отрицает — они есть. Но не надо приписывать php то, что его виной не является.
                      • 0
                        Насчет Java вопрос спорный, конечно, но в целом — согласен. Грамотно выбранные фреймворки нивелируют подавляющее большинство нестыковок в языке (которые есть и в руби, и в питоне), что позволяет вполне себе комфортно разрабатывать. Не идеал, но на 4+ вполне сойдет.
                  • 0
                    PHP — помойка, что уж скрывать. Да и создатель языка этого не скрывает. Ещё не видел ни одного языка с такой «играй гармонь» философией. Достаточно сравнить сигнатуры различных функций из одной и той же стандартной библиотеки.
          • 0
            Этот код писал человек-обфускатор. Зачем-то он умышленно запутывал код.
      • +4
        + к неявностям

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


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

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

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

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

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

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

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

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

                    Первая — в языке уже предусмотрены как минимум две человекопонятные конструкции, работающие одинаково во всех языках программирования:
                    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 пустое.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                          4. Печально, что для вас мир полон непрофессионалов и неприятных конструкций.
                          • +2
                            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%)

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

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

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

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

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

                                В целом я понял вашу позицию. Тесты проводил с целью понять, может быть, я и правда заблуждаюсь и для перехода на такие конструкции и правда есть какие-то причины. Но нет, остаюсь на традиционных.
                                • 0
                                  Приведите тогда ваши примеры удобочитаемого кода известных продуктов — посмотреть, сравнить.
                            • +1
                              Лично я не вижу ничего смешного в конструкции:
                              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? :)

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

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

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

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

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

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

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

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

                                Вероятно, но тогда придётся тестировать каждое такое решение, а я слегка устал этим заниматься. Кроме того, я вижу и использование в этих же топ-50 стандартных условных конструкций для тех же целей, что наталкивает на мысль, что никакой конвенции нет.
          • +2
            Вот именно, что извращенный. Чтобы понять смысл такого нужно разобрать с конца все операции, понять, что смысл !~x это x != -1 и убедиться, что это всегда так, без исключений когда x например undefined или null.
            • 0
              ~undefined === -1

              ~null === -1

              ~NaN === -1

              ~Infinity === -1

              ~-Infinity === -1
              • +2
                > !~undefined
                false
                > undefined != -1
                true
                > !~null
                false
                > null != -1
                true
                

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return list.filter(function (item) { return item.indexOf(query) != -1; });
  • +2
    К сожалению я не застал то время когда отмороженные перловики кичились сложностью своего языка, гордясь тем, что нужно много ума, чтобы распарсить некоторые его конструкции. Но глядя на сегодняшних джаваскриптеров я примерно представляю как это выглядело.
  • 0
    Еще распространенная ошибка при live поиске — это
    отсутствие учета вводимых символов с клавиатуры.

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

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

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