Статьи

WebAssembly просрочен: мысли о JavaScript для крупных проектов

На Auth0 большая часть нашего программного обеспечения разрабатывается с использованием JavaScript. Мы интенсивно используем язык как на передней, так и на задней части.

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

JavaScript как язык общего назначения

То, что молодым разработчикам сегодня может показаться очевидным, в прошлом не было так ясно: можно ли считать JavaScript языком общего назначения? Я думаю, что мы можем с уверенностью согласиться, что ответ на этот вопрос сегодня — «да». Но JavaScript не совсем молодой: он родился в 1995 году, более 20 лет назад!

На протяжении более 15 лет JavaScript не пользовался популярностью за пределами Интернета, где он в основном использовался для фронт-энда. Многие разработчики считают, что JavaScript — это не что иное, как необходимый инструмент для реализации их мечты о более интерактивных и отзывчивых сайтах. Неудивительно, что даже сегодня JavaScript не имеет переносимой модульной системы во всех распространенных браузерах (хотя операторы import / export являются частью последней спецификации). Таким образом, в некотором смысле, разработка JavaScript медленно развивалась, так как все больше и больше разработчиков находили способы расширить его использование.

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

Краткий обзор прогресса JavaScript в течение многих лет

JavaScript начал свою жизнь как клейкий язык для Интернета. Создатели Netscape Navigator (основного веб-браузера в 90-х годах) думали, что язык, который могут использовать дизайнеры и программисты, занятые неполный рабочий день, сделает сеть гораздо более динамичной. Так что в 1995 году они привели Брендана Эйха на борт. Задача Эйха состояла в том, чтобы создать подобный Схеме язык для браузера. Если вы не знакомы со Scheme, это очень простой язык из семейства Lisp. Как и во всех Лиспах, в Scheme очень мало синтаксиса, что облегчает его выбор.

Однако все было не так гладко. В то же время Sun Microsystems настаивала на интеграции Java в веб-браузеры. Конкуренция со стороны Microsoft и их собственных технологий тоже не помогла. Итак, JavaScript нужно было разрабатывать на скорую руку. Более того, развитие Java заставило Netscape захотеть, чтобы их новый язык стал дополнением к нему.

Эйх был вынужден придумать прототип как можно скорее; некоторые утверждают, что это было сделано в течение нескольких недель. Результатом стал динамический язык с синтаксисом, похожим на Java, но с совершенно другой философией. Для начала, объектная модель в этом новом языке полностью отличалась от объектной модели Java, полученной из Simula. Этот первоначальный прототип языка был известен как Mocha, а позже как LiveScript.

LiveScript был быстро переименован в JavaScript так же, как он был запущен, по маркетинговым причинам. Java была на подъеме, и наличие «Java» в названии может вызвать дополнительный интерес к языку.

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

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

Следующие несколько версий JavaScript были направлены на то, чтобы сделать его широко доступным. Одним из первых шагов, предпринятых для достижения этого, было сделать его стандартом. Таким образом, работа по стандартизации началась через ECMA, а затем через ISO. ECMAScript, название которого было принято после стандартизации, было очень похоже на первые версии JavaScript, включенные в Netscape Navigator. Только в ECMAScript 3 или JavaScript 1.5 в 1999 году была завершена разработка большей части JavaScript, который мы знаем и используем сегодня. Эта версия включает обработку исключений, instanceof, все общие механизмы управления (do / while, switch), eval и большинство встроенных функций и объектов (Array, Object и т. Д.).

Темный период начался после этого для JavaScript. Конкурирующие группы имели разные идеи для разработки JavaScript. Некоторые выступали за расширенные функции, такие как модули, своего рода статическая типизация и объектно-ориентированное программирование на основе классов. Другие думали, что это было слишком. Было сделано предложение для ECMAScript 4, и разработчики начали интегрировать некоторые функции в свои движки. К сожалению, сообщество никогда не решало, какие функции включить. Microsoft также работала над JScript, реализацией JavaScript с расширениями. В результате ECMAScript 4 был заброшен.

Лишь в 2005 году началась разработка JavaScript. Уточнения к ECMAScript 3 были сделаны. Несколько других функций (пусть, генераторы, итераторы) были разработаны вне стандарта. Смятение, вызванное неудачной спецификацией ECMAScript 4, было решено, и в 2009 году было решено, что уточнения к ECMAScript 3 следует переименовать в ECMAScript 5. Был определен путь для будущей разработки, и многие функции, предложенные для версии 4, начали переоцениваться.

Текущая версия стандарта ECMAScript 7 (также известная как 2016 г.) включает некоторые функции, предназначенные для версии 4, такие как классы и операторы импорта / экспорта. Эти функции предназначены для того, чтобы сделать JavaScript более приемлемым для средних и крупных систем. Это было обоснование ECMAScript 4 в конце концов. Но оправдывает ли JavaScript это обещание?

Давайте посмотрим на не очень объективное краткое изложение функций JavaScript.

Особенности языка: Хороший

Синтаксическое знакомство

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

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

function test(a, b, c) {
  a.doStuff(b.property, c);
  return a.property;
}

Асинхронная природа

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

Решение: обратные вызовы и замыкания.

 const consumer = new Consumer();

$.ajax({
  method: "GET",
  url: "http://test.com/resource"
}).done(function(data) {
  consumer.push(data);
});

Можно утверждать, что JavaScript получил преимущество при разработке на стороне сервера благодаря преимуществам этого подхода. Такие функции, как async / await, сделают асинхронную разработку еще проще.

Функциональные особенности и замыкания

Многопарадигмальный подход JavaScript оправдал себя. Многие языки, глубоко укоренившиеся в одной парадигме, такие как Java, начали реализовывать другие парадигмы. У JavaScript это было с самого начала. Наследование прототипа является достаточно мощным для реализации всей семантики ООП. Замыкания позволяют обрабатывать функции как первоклассные объекты и передавать их как таковые. Объекты и массивы с удобной системой обозначений (JSON) в сочетании с этими функциями делают JavaScript по своей природе мощным.

Ниже приведен пример, взятый из документации RxJS:

 const source = getAsyncStockData();

const subscription = source
 .filter(quote => quote.price > 30)
 .map(quote => quote.price)
 .forEach(price => console.log(`Prices higher than $30: ${price}`);

Особенности языка: плохой

Причуды

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

 function test() {
  functionCall();
  obj.operation();

  // Other code

  return  //<-- semicolon inserted here, returns undefined
    {
      key: "This object should be returned instead"
    }
}

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

Слабый набор текста и автоматические преобразования

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

Крайний пример того, как работает JavaScript-приведение, можно увидеть ниже:

 console.log((![]+[])[+!![]]); 
//This prints the character “a”

Это возможно благодаря возможности приведения любого значения к логическому значению. Первое неявное приведение приводит к тому, что значение «false» приводится к целому числу, а затем индексируется до его второго значения. Лукавый и безумный.

Модульная система

ECMAScript 6 (2015) окончательно определил синтаксис потенциальной модульной системы . Тем не менее, ни один браузер в настоящее время не реализует это в удобной форме. Другими словами, даже сегодня требуются внешние загрузчики модулей.

Модули необходимы для правильной разработки программного обеспечения. Стандартный способ разделения и повторного использования кода является, пожалуй, одним из самых фундаментальных его аспектов. Что касается модулей JavaScript , мы по-прежнему используем конкурирующие решения: require (модули Node.js), import / export плюс загрузчик модулей или транспортер (Babel, System.js , Webpack) или даже просто старые, сразу вызванные функции или UMD. ,

Глобалы и подъем

Переменные JavaScript всегда определяются в области действия функции (если для их объявления не используется let, последнее добавление). Это может привести к неожиданным изменениям переменных. Легко представить, как неожиданные изменения переменных могут быть проблематичными для крупномасштабной разработки.

 function test() {
  if (true) {
    var a = 1;
    console.log(a);
  }

  var a;
  if (a) {
    // This code runs, a === 1
    console.log("Here");
  }
}

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

 function test() {
  variable = "test";
}
test();
console.log(window.variable);

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

Отсутствие правильных целочисленных типов

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

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

По этой причине текущие версии JavaScript частично содержат типизированные массивы. Однако этого недостаточно.

Мнения о разработке большой системы с использованием JavaScript

На Auth0 большая часть нашего программного обеспечения разрабатывается с использованием JavaScript. Мы вложили значительные средства в Node.js на ранней стадии. Пока что это окупилось. Но у некоторых из наших самых старших разработчиков много историй из окопов.

Мы попросили Дамиана Шенкельмана, директора по техническим вопросам, и Жозе Романиелло, руководителя по техническим вопросам, поделиться своими мыслями по этому вопросу.

Разработчик окружен руками с микрофонами, берущими у него интервью об использовании JavaScript для больших проектов

Q: Каково ваше мнение о JavaScript как о языке общего назначения?

Д. Шенкельман : Мне нравится язык, так как он имеет очень маленький базовый набор понятий, и замыкания являются очень мощной функцией, на которой вы можете опираться.

Очевидно, есть недостатки: неявные преобразования типов и слабая система типов. Я считаю, что если вы придерживаетесь хороших сторон , JavaScript может быть хорошим языком. Конечно, тесты также являются неотъемлемой частью разработки.

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

Сам язык довольно прост для понимания, а также платформы, на которых он работает, а именно браузер или Node.js. Настоящая мощь JavaSacript исходит из экосистемы инструментов, библиотек и огромного сообщества.

Я думаю, что Node.js правильно понял свою философию (может быть, из Unix?), Маленькое ядро ​​и обширное пользовательское пространство.

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

Я думаю, что это не может быть связано с менеджером пакетов или языком, но это больше похоже на фактический способ добиться цели. Другие технологии имеют аналогичные инструменты, но вместо библиотек они имеют «каркасы» типа «все или ничего», такие как Spring, WCF и т. Д.

В: За годы разработки высокопроизводительного сервиса с использованием JavaScript, можете ли вы вспомнить какие-нибудь истории из окопов, где JavaScript полностью мешал или полностью спасал день?

Д. Шенкельман : На самом деле, я нахожу, что большинство больших ошибок, которые мы сделали, были связаны с отсутствующими свойствами или объектами неправильного типа. Это ошибки, которых легко избежать путем неявной проверки типов и которые требуют большей дисциплины для написания тщательных тестов на JavaScript. Я думаю, что прогрессивная типизация может очень помочь в этих случаях. К сожалению, мы не разработали новый набор руководящих принципов, чтобы начать делать это сегодня, но это то, что мы решительно рассматриваем. Я думаю, что TypeScript — это шаг в правильном направлении, особенно когда речь идет о межмодульных контрактах. Конечно, это не значит, что TypeScript должен заменить тесты: совсем нет, но он может помочь обнаружить глупые ошибки. Линтеры также очень помогают.

J. Romaniello : Matias, Eugenio, Iaco и я пришли из совершенно другого мира до Auth0. Мы были разработчиками .NET много лет. Запуск Auth0 с Node.js позволил нам развиваться невероятными темпами по сравнению с другими языками, потому что у нас был JavaScript в базе данных (Mongo), JavaScript в бэкэнде (Node) и JavaScript в браузере. Использование строго типизированного языка с базой данных на основе схемы обычно требует написания адаптеров и отображения из одной модели в другую. В JavaScript вы используете «hash-maps» до конца.

Я не могу приписать какой-либо конкретный сбой самому языку. Мы допустили много ошибок, например, мы изучили сложный способ, которым зацикливание более 100 тыс. Объектов для визуализации чего-либо блокирует цикл обработки событий.
Что касается Node.js, мы иногда хотим иметь более подробные ошибки. Есть случаи, когда вы просто получаете исключение «ECONNRESET» без каких-либо других подробностей. К счастью, кодовую базу Node.js легко понять, и это позволило мне исправить это.

В: Если бы вы могли выбрать какой-либо язык или структуру для разработки серверной части, такой как Auth0, с нуля, какой сейчас будет язык или структура? Вы бы выбрали Node.js и JavaScript снова?

Д. Шенкельман : Думаю, это не так важно, как может показаться. Я имею в виду, что разработка платформы, особенно когда речь идет о стартапах, — это гораздо больше, чем просто программирование. Код — это просто средство для достижения вашего продукта. Пока набор инструментов может быть разумно применен к рассматриваемой проблемной области, кодирование является лишь еще одной частью головоломки. Вы добьетесь результатов, выберете ли вы Java, JavaScript, C # или многие другие из опробованных и протестированных платформ.

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

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

J. Romaniello : Бэкэнд Auth0 превращается в небольшие сервисы. Это позволяет нам автоматически масштабировать при различных типах нагрузки, дает нам улучшенную отказоустойчивость, лучший мониторинг и т. Д. Мы используем Node.js, но по-другому, чем когда мы начинали. Я думаю, что я бы выбрал либо Node.js снова или что-то вроде Erlang / Elixir.


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

Введите WebAssembly

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

WebAssembly может изменить все это. Представьте себе возможность выбора проверенных и проверенных библиотек внутри вашей компании для вашего нового проекта. У вас есть внутренняя библиотека алгоритмов, реализованная в C? Нет проблем, скомпилируйте его в WASM и загрузите в свое приложение. Затем разработайте те части, которые являются разумными в JavaScript. Это та сила, которой не хватало в Интернете в течение многих лет, и она, наконец, не за горами. И не только для фронтэнда. Ожидается, что Node.js также позволит загружать модули WASM. В некотором смысле WebAssembly — это метаморфоза виртуальных машин JavaScript от языковых к общим виртуальным машинам.

Блок-схема процесса WebAssembly

С момента выпуска платформы .NET в 2002 году виртуальные машины общего назначения резко возросли. Java, например, стала платформой для новых и существующих языков. Scala и Clojure, пожалуй, самые большие представители этой тенденции. Абсолютно новые платформы были разработаны с учетом преимуществ наличия набора проверенных инструментов в сочетании с подходящим языком для решения проблемы. И JavaScript стал богатой платформой.

Последние несколько месяцев в WebAssembly были захватывающими: Binaryen, новая инфраструктура компилятора для генерации файлов WASM начала работать; Firefox, Chrome и Edge имеют рабочие реализации WebAssembly за экспериментальными флагами; Спецификация и проектные документы выросли в размерах. Вы можете попробовать даже полноценную демоверсию с ASM.js на примере Unity. WebAssembly прямо за углом, но он все еще не готов.

Между тем, огромные приложения разрабатываются на JavaScript из-за необходимости или отсутствия гибкости. Чем больше приложение, тем больше вероятность того, что вы достигнете предела: большая целочисленная математика, SIMD, многопоточность и т.д.

Вывод

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

Однако отсутствие гибкости в интеграции с другими решениями вынуждает JavaScript работать там, где он не подходит для работы. Если у вас есть только молоток, все выглядит как гвоздь.

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