Почему человек из мира Java стал горячим сторонником Node.js и JavaScript?

https://blog.sourcerer.io/why-is-a-java-guy-so-excited-about-node-js-and-javascript-7cfc423efb44
  • Перевод
Дэвид Хэррон, автор материала, перевод которого мы публикуем сегодня, задался следующим вопросом: «Должен ли человек, работавший более 10 лет в Sun Microsystems, в команде Java SE, до последнего вздоха думать лишь о байт-коде Java и создавать экземпляры абстрактных интерфейсов?». Он задавал этот вопрос применительно к себе, и для него платформа Node.js, после Java, оказалась подобна глотку свежего воздуха. Дэвид говорит, что когда он был уволен из Sun в январе 2009 года (прямо перед поглощением этой компании Oracle), он узнал о Node.js. Эта технология его зацепила. Что значит «зацепила»? С 2010-го года он много писал о программировании для Node.js. А именно, написал несколько книг, в том числе — «Node.js Web Development», четвёртое издание которой вышло в этом году. Он подготовил множество небольших материалов о Node.js, опубликованных в интернете. Фактически, он уделил очень много времени и сил, рассказывая о платформе Node.js и о возможностях JavaScript. Почему того, кто раньше занимался исключительно Java, так увлекли Node.js и JavaScript?

image

О мире Java


Работая в Sun, я верил в технологию Java. Я делал доклады на JavaONE, участвовал в разработке класса java.awt.Robot, занимался организацией мероприятия Mustang Regressions Contest (это был конкурс, направленный на поиск ошибок в Java 1.6), помогал в запуске проекта «Distributions License for Java», который служил ответом на вопрос о Linux-дистрибутивах JDK до появления OpenJDK. Позже я играл некоторую роль в запуске проекта OpenJDK. Попутно я, в течение примерно 6 лет, публиковал материалы в блоге на java.net (теперь этот сайт закрыт). Это были 1-2 статьи в неделю, посвящённые значимым событиям в экосистеме Java. Значительную роль в моей деятельности играла защита Java от тех, кто предрекал этой технологии мрачное будущее.


Эту награду, Duke Award, давали особо отличившимся сотрудникам Sun. Мне она досталась после того, как я организовал Mustang Regressions Contest

Что произошло с человеком, который так много занимался всем тем, что связано с Java? Собственно говоря, тут я и хочу рассказать о том, как я превратился из приверженца Java в горячего сторонника Node.js и JavaScript.

Надо сказать, что то, что со мной произошло, нельзя назвать полным отказом от Java. Я, за последние 3 года, написал довольно много кода на Java, пользовался Spring и Hibernate. Хотя то, что я теперь делаю в этой области, мне очень нравится (я работаю в индустрии солнечной энергетики, занимаюсь тем, чем мне заниматься приятно, например — пишу запросы для работы с данными из сферы энергетики), программирование на Java в моих глазах теперь лишилось былого великолепия.

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

Вот, вкратце, основные идеи, которых я коснусь в этом материале:

  • Программы на Java полны шаблонного кода, который скрывает намерения программиста.
  • Работа со Spring и Spring Boot дала мне хороший урок, который заключается в том, что попытки скрыть сложные механизмы приводят к появлению ещё более сложных конструкций.
  • Платформа Java EE была проектом, созданным, так сказать, «всеобщими усилиями», который покрывает абсолютно все нужды разработки корпоративных приложений. В результате платформа Java EE оказалась непомерно сложной.
  • Разработка с использованием Spring — это, до определённого момента, занятие приятное. Эта иллюзия исчезает в тот день, когда из глубин некоей подсистемы, о которой вы никогда не слышали, всплывает исключение, которое совершенно невозможно понять, и на то, чтобы выяснить, в чём же заключается проблема, уходит не меньше трёх дней.
  • Какие вспомогательные механизмы, создающие излишнюю нагрузку на систему, нужны фреймворку, который умеет «писать» код за программистов?
  • Хотя IDE вроде Eclipse — это мощные приложения, они являются показателем сложности экосистемы Java.
  • Платформа Node.js появилась в результате усилий одного человека, направленных на совершенствование его видения легковесной архитектуры, управляемой событиями.
  • Кажется, что сообщество JavaScript с энтузиазмом воспринимает идеи избавления от шаблонного кода, что позволяет программистам максимально ясно выражать свои намерения.
  • В качестве решения проблемы ада коллбэков в JS выступает конструкция async/await, которая является примером отказа от шаблонного кода и способствует ясности выражения намерений программистов.
  • Программирование для Node.js — это сплошное удовольствие.
  • В JavaScript нет строгой типизации, характерной для Java. Это — благословение и проклятие языка. Это позволяет легче писать код, но, для того, чтобы проверить его правильность, приходится уделять тестированию больше времени.
  • Системой управления пакетами, представленной npm/yarn, легко и приятно пользоваться. Она не идёт ни в какое сравнение с Maven.
  • И Java, и Node.js предлагают отличную производительность. Это идёт вразрез с мифом, в соответствии с которым JavaScript — это медленный язык, использование которого приводит к низкой производительности платформы Node.js.
  • Производительность Node.js опирается на усилия Google по совершенствованию V8, движка, от которого зависит скорость работы браузера Chrome.
  • Жестокая конкуренция между производителями браузерных JS-движков способствует развитию JavaScript, а это очень выгодно Node.js.

О проблемах разработки на Java


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

Spring — это популярный фреймворк для разработки веб-приложений, основанных на Java.

Основная цель Spring, и, в частности, Spring Boot, заключается в предоставлении возможности пользоваться заранее настроенным стеком Java EE. Программист, который пользуется Spring, не должен, для того, чтобы создать готовую систему, заботиться о сервлетах, о системах постоянного хранения данных, о серверах приложений, и ещё неизвестно о чём. Все эти заботы перекладываются на плечи Spring, а программист занимается написанием кода, реализующего логику приложения. Например, механизмы JPARepository ответственны за генерирование запросов к базам данных для методов, названия которых выглядят как findUserByFirstName. Код таких методов программисту писать не приходится. Достаточно передать системе описание метода, а всё остальное сделает Spring.

Звучит всё это очень хорошо, работать в таком стиле приятно, но — до тех пор, пока не случится какая-нибудь неожиданность.

Я имею в виду ситуацию, когда, например, приходит исключение Hibernate PersistentObjectException с сообщением detached entity passed to persist. Что бы это могло значить? На то, чтобы это выяснить, ушло несколько дней. Как оказалось, если описать всё очень упрощённо, это значит, что JSON-данные, поступившие в конечную точку REST, имеют поля ID с некими значениями. Hibernate, опять же, если не вдаваться в детали, стремится контролировать значения ID, и, в результате, выбрасывает вышеописанное малопонятное исключение. Существуют тысячи таких вот сообщений об ошибках, сбивающих с толку и сложных для восприятия. Учитывая то, что в Spring имеются целые каскады подсистем, основанных друг на друге, стек Spring похож на заклятого врага программиста, который наблюдает за ним и ждёт, когда программист допустит малейшую ошибку, а когда это случается, бросается в него исключениями, не совместимыми с нормальной работой приложения.

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

Как выполняется метод findUserByFirstName, учитывая то, что программист не писал код подобного метода? Фреймворку нужно разобрать имя метода, понять намерение программиста, создать нечто вроде абстрактного синтаксического дерева, сгенерировать какой-то SQL-код, и так далее. Как всё это нагружает систему? И всё это существует лишь для того, чтобы программисту не нужно было бы писать код?

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

Слоган «Compatibility Matters» скрывал в себе замечательную идею, в соответствии с которой важнейшей особенностью платформы Java была обратная совместимость. Мы относились к этому серьёзно, нанося на футболки изображения подобные тому, которое вы можете видеть ниже.


Обратная совместимость — это очень важно

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

Java и Node.js


Spring и Java EE чрезмерно усложнены. Платформа Node.js на их фоне воспринимается как глоток свежего воздуха. Первое, на что обращаешь внимание, знакомясь с Node.js — это подход Райана Даля к разработке ядра платформы. Его опыт подсказал ему, что платформы, использующие потоки, нужны для создания сложных, тяжеловесных систем. Он же искал чего-то другого, и потратил пару лет на совершенствование набора базовых механизмов, воплощённых в Node.js. В результате у него получилась легковесная система, которую характеризует один поток выполнения, изобретательное использование анонимных функций JavaScript в роли асинхронных коллбэков, и библиотека времени выполнения, оригинально реализующая асинхронные механизмы. Изначальным посылом при создании такой системы было обеспечение высокой производительности обработки событий с доставкой этих событий в функции обратного вызова.

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

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

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

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

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

Решения проблем и проблемы решений


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

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

Рассмотрим пример:

const async = require(‘async’);
const fs = require(‘fs’);
const cat = function(filez, fini) {
  async.eachSeries(filez, function(filenm, next) {
    fs.readFile(filenm, ‘utf8’, function(err, data) {
      if (err) return next(err);
      process.stdout.write(data, ‘utf8’, function(err) {
        if (err) next(err);
        else next();
      });
    });
  },
  function(err) {
    if (err) fini(err);
    else fini();
  });
};
cat(process.argv.slice(2), function(err) {
  if (err) console.error(err.stack);
});

Это — невзрачная имитация команды Unix cat. Библиотека async отлично проявляет себя в деле упрощения последовательностей асинхронных вызовов. Однако её использование требует большого объёма шаблонного кода, скрывающего намерение программиста.

В сущности, этот код содержит цикл. Он не написан как обычный цикл, здесь не используются естественные конструкции описания циклов. Далее, результаты выполнения кода и выдаваемые им ошибки не попадают туда, куда им правильно было бы попадать. Они оказываются запертыми в коллбэках, а это неудобно. Но, до появления в Node.js реализации стандартов ES2015/2016, ничего лучшего сделать было нельзя.

Если переписать этот код с учётом новых возможностей, которые, в частности, имеются в Node.js 10.x, то получится следующее:

const fs = require(‘fs’).promises;
async function cat(filenmz) {
  for (var filenm of filenmz) {
    let data = await fs.readFile(filenm, ‘utf8’);
    await new Promise((resolve, reject) => {
      process.stdout.write(data, ‘utf8’, (err) => {
        if (err) reject(err);
        else resolve();
      });
    });
  }
}
cat(process.argv.slice(2)).catch(err => { 
    console.error(err.stack); 
});

В этом примере мы воспользовались конструкцией async/await. Здесь представлены те же самые асинхронные механизмы, что и в предыдущем примере, но тут применяются обычные структуры, используемые при организации циклов. Работа с результатами и ошибками выглядит вполне обычным образом. Такой код легче читать и писать. Этот подход позволяет легко понять намерение программиста.

Единственный недостаток заключается в том, что process.stdout.write не имеет Promise-интерфейса, в результате этот механизм нельзя использовать в async-функциях без оборачивания его в промис.

Теперь можно сделать вывод о том, что проблема ада коллбэков в JavaScript была решена способом, который отличается от попытки скрытия сложных механизмов. Вместо этого внесены изменения в язык, что решило и саму проблему, и избавило нас от неудобств, вызванных необходимостью использовать большие объёмы шаблонного кода во временном решении. Кроме того, с применением механизма async/await код попросту стал красивее.

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

Строгая типизация, интерфейсы и мнимая ясность кода


В те времена, когда я занимался защитой Java от разного рода нападок, я напирал на то, что строгая типизация позволяет писать огромные приложения. В те времена в ходу была разработка монолитных систем (не было микросервисов, не было Docker и тому подобных вещей). Так как Java является языком со строгим контролем типов, компилятор Java помогает программисту избегать множества проблем, не давая ему скомпилировать неправильный код.

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

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

Проблема программирования на языке со строгой типизацией столь велика, что программист, практически без вариантов, должен использовать большую сложную IDE. Простого редактора кода тут недостаточно. Единственный способ поддержания Java-программиста в адекватном состоянии (за исключением пиццы) заключается в постоянном показе ему выпадающих списков, содержащих доступные поля объектов или описания параметров методов. Этот и другие вспомогательные механизмы таких IDE, как Eclipse, NetBeans, или IntelliJ, помогают в деле создания классов, облегчает рефакторинг и решение других задач.

И… не буду говорить о Maven. Это — просто кошмарный инструмент.

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

Относится ли вышеизложенное к плюсам Java или к минусам — зависит от точки зрения.

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

Борьба с ошибками при помощи маленьких модулей, которые легко тестировать


Node.js подталкивает программиста к тому, чтобы он разбивал свои проекты на небольшие фрагменты, на так называемые модули. Возможно, этот факт покажется вам незначительным, но он частично решает только что упомянутую нами проблему.

Вот основные характеристики модуля:

  • Самостоятельность. Модуль объединяет взаимосвязанный код в единую сущность.
  • Чёткие границы. Код внутри модуля защищён от вмешательства в его работу каких-либо внешних механизмов.
  • Явный экспорт. По умолчанию код и данные модуля не экспортируются. Разработчик самостоятельно решает, какие функции и данные нужно сделать общедоступными.
  • Явный импорт. Программист, при разработке модуля, сам решает, от каких модулей он будет зависеть.
  • Потенциальная независимость. Модули можно сделать общедоступными, в весьма широком смысле этого слова, публикуя их в npm, или, если они предназначены для внутренних нужд компании, публикуя в закрытых репозиториях. Это позволяет легко использовать одни и те же модули в разных приложениях.
  • Простота понимания кода. То, что модули имеют небольшие размеры, упрощает чтение и понимание их кода, открывает возможность для свободных дискуссий о них.
  • Облегчение тестирования. Маленький модуль, если он реализован правильно, легко поддаётся модульному тестированию.

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

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

Ещё одно решение проблемы динамической типизации в JavaScript заключается в тщательном тестировании кода.

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

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

В итоге можно сказать, что использование модульного подхода к разработке — это сильная сторона Node.js и JavaScript.

Управление пакетами


Мне плохо от одной мысли о Maven, поэтому я не могу даже нормально писать о нём. И, насколько я понимаю, Maven, без компромиссов, либо любят, либо ненавидят.

Проблема тут заключается в том, что в среде Java нет целостной системы для управления пакетами. Пакеты Maven существуют, с ними можно нормально работать, их поддерживает Gradle. Но то, как организована работа с ними, и близко не похоже на те удобства, которые даёт разработчику система управления пакетами для Node.js.

В мире Node.js существуют два отличных менеджера пакетов, которые работают в тесной связи друг с другом. Сначала единственным подобным инструментом был репозиторий npm и одноимённый инструмент командной строки.

Благодаря npm в нашем распоряжении имеется отличная схема для описания зависимостей пакетов. Зависимости могут быть строгими (скажем, указывается, что нужна исключительно версия 1.2.3 некоего пакета), или заданными с несколькими степенями свободы — вплоть до *, что означает использование самой свежей версии некоего пакета.

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

Система npm получилась настолько удачной, что пользуются ей не только разработчики серверных продуктов на Node.js, но и программисты, занимающиеся фронтендом. Раньше там, для управления пакетами, использовались инструменты вроде Bower. Bower был признан устаревшим, и теперь можно обнаружить, что все JS-библиотеки для разработки фронтенда существуют в виде npm-пакетов. Многие вспомогательные инструменты для клиентской разработки, вроде Vue.js CLI и Webpack, написаны в виде Node.js-приложений.

Ещё одна система управления пакетами для Node.js, yarn, загружает пакеты из репозитория npm и использует такие же конфигурационные файлы. Основное преимущество yarn перед менеджером пакетов npm заключается в более высокой скорости работы.

Репозиторий npm, независимо от того, работают ли с ним с помощью менеджера пакетов npm или c помощью менеджера пакетов yarn, представляет собой мощную основу того, что делает разработку для Node.js такой простой и приятной.


Однажды, после того, как я помогал в разработке java.awt.Robot, я вдохновился на создание этой вот штуки. В то время как официальное изображение Duke состоит из кривых, RoboDuke построен из прямых линий. Только локтевые суставы у этого робота круглые

Производительность


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

И у Java, и у JavaScript есть причины стремиться к высокой производительности. Если говорить о Java и о Node.js, то их роднит стремление к быстрому серверному коду. В случае с браузерным JavaScript стимулом к высокой производительности является повышение качества клиентских приложений. Мы поговорим об этом в разделе о насыщенных интернет-приложениях.

JDK Sun/Oracle использует HotSpot — виртуальную машину, поддерживающую множество стратегий компиляции байт-кода. Название этой виртуальной машины намекает на технику оптимизации, в ходе реализации которой обнаруживается код, который выполняется чаще всего, и к такому коду, чем более интенсивно он используется, применяется всё больше оптимизаций. HotSpot — это высокооптимизированная система, которая производит очень быстрый код.

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

Node.js следует в том же направлении, так как он использует движок V8 браузера Google Chrome.

В качестве примера можно привести это выступление Питера Маршалла, инженера Google, который занимается работой над V8, и основной задачей которого является улучшение производительности этого движка. Здесь он рассказывает о том, почему V8 перешёл с Crankshaft на Turbofan.

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

Из этого видео можно узнать об использовании в JavaScript новой библиотеки, TensorFlow.js. API этой библиотеки похоже на API TensorFlow для Python, она поддерживает импорт предварительно обученных моделей. Эту библиотеку можно использовать, например, для анализа видео с целью распознавания объектов, при этом все необходимые вычисления выполняются в браузере.

Вот выступление Криса Бэйли из IBM, где он затрагивает вопросы производительности и масштабируемости Node.js, в частности, при использовании конфигураций, основанных на Docker/Kubernetes. Он начинает рассказ с рассмотрения набора бенчмарков, которые демонстрируют значительно более высокую производительность Node.js в сравнении со Spring Boot. Речь идёт о пропускной способности подсистемы ввода-вывода, о времени запуска приложения и о потреблении памяти. Более того, от релиза к релизу производительность Node.js серьёзно улучшается, отчасти, благодаря улучшениям, вносимым в V8.

Здесь Бэйли говорит, что Node.js не подходит для выполнения интенсивных вычислений. Нам важно понять причину подобной рекомендации. Из-за того, что в Node.js используется однопоточная модель выполнения кода, длительные вычисления блокируют обработку событий. Я, в моей книге «Node.js Web Development», затрагиваю эту проблему, приводя три подхода к её решению:

  • Рефакторинг алгоритмов — выявление медленных частей алгоритма и его рефакторинг для достижения более высокой скорости работы.
  • Разбиение процесса вычислений на небольшие фрагменты с использованием диспетчера событий, что позволяет Node.js регулярно возвращаться к главному потоку.
  • Передача тяжёлых вычислительных задач на вспомогательный сервер.

Если производительности JavaScript для ваших задач недостаточно, взгляните на следующие два способа интеграции нативного кода в Node.js. Самый простой способ — это использование нативных Node.js-модулей. В наборе вспомогательных инструментов для Node.js-разработки имеется средство node-gyp, которое помогает работать с такими модулями. Вот видео, в котором демонстрируется интеграция Rust-библиотеки с Node.js.

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

Насыщенные интернет-приложения


Насыщенные интернет-приложения (Rich Internet Applications, RIA) были у всех на слуху лет десять назад. Тогда говорили о том, что они, реализованные на базе быстрых (для своего времени) JS-движков, способны сделать неактуальными традиционные настольные приложения.

На самом деле, эта история началась более 20 лет назад. Sun и Netscape договорились об использовании Java-апплетов в Netscape Navigator. JavaScript был, отчасти, разработан как скриптовый язык для Java-апплетов. Тогда индустрия надеялась на то, что на серверах будут использоваться Java-сервлеты, на клиентах — Java-апплеты. В результате разработчики попадут в замечательную ситуацию, когда и для серверов и для клиентов можно будет писать на одном и том же языке. Этого не случилось по разным причинам.

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

Сегодня мы начинаем видеть, как идея RIA начала приносить плоды. Благодаря Node.js стала реальной та желанная для многих ситуация, когда, и на сервере и на клиенте, используется один и тот же язык. Только теперь это JavaScript.

Вот несколько примеров:

  • Набор приложений Google Docs (использованный при написании этой статьи), который очень похож на типичный комплект офисных приложений, но работающий в браузере.
  • Мощные фреймворки, вроде React, Angular и Vue.js, упрощают разработку браузерных приложений, основанных на HTML, CSS и JavaScript.
  • Electron — это смесь Node.js и браузера Chromium. Данный фреймворк предназначен для разработки кросс-платформенных настольных приложений. С его использованием созданы такие весьма популярные и качественные приложения, как Visual Studio Code, Atom, GitKraken, и Postman.
  • Так как в Electron/NW.js применяется браузерный движок, веб-фреймворки, такие, как React, Angular, и Vue, можно использовать для разработки настольных приложений.

Технология Java, в роли платформы для разработки настольных приложений, умерла не из-за насыщенных интернет-приложений, написанных на JavaScript. Она умерла, преимущественно, из-за невнимания к клиентским технологиям в Sun Microsystems. В центре внимания Sun были корпоративные пользователи, которым нужна высокая производительность серверных приложений. Я видел всё это своими глазами. Настоящим убийцей Java-апплетов стала проблема в безопасности, выявленная несколько лет назад в плагине Java и в Java Web Start. Это привело ко всемирному сокращению использования Java-апплетов и Webstart-приложений.

Другие виды настольных приложений всё ещё можно разрабатывать на Java, и, как результат, IDE NetBeans и Eclipse всё ещё борются друг с другом. Однако в этой сфере применения Java наблюдается застой, и за пределами инструментов разработки существует очень немного приложений, основанных на Java.

Исключением является технология JavaFX.

Технология JavaFX, 10 лет назад, планировалась как ответ Sun на появление iPhone. Планировалось, что эта технология позволит разрабатывать на платформе Java, доступной на мобильных устройствах, приложения, обладающие насыщенным графическим интерфейсом. Это позволило бы одним махом вывести из игры Flash и средства разработки приложений для iOS. Но ничего такого не произошло. JavaFX используется до сих пор, но эта технология не видела столь масштабного взлёта, на который рассчитывали её создатели. В наши дни всеобщее внимание притягивают веб-фреймворки вроде React, Vue.js и им подобных.

В описанной ситуации JavaScript и Node серьёзно обогнали Java.

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


Кольцо Java


Инструкция к кольцу

Итоги


В наши дни разработчикам серверных проектом есть из чего выбирать. Индустрия больше не ограничена так называемыми «P-языками» (Perl, PHP, Python) и Java, так как теперь есть платформа Node.js, есть языки Ruby, Haskell, Go, Rust, и многие другие. В результате у серверных программистов теперь есть просто огромный выбор замечательных технологий.

Если же говорить о том, почему я, человек, который жил исключительно Java, перешёл на сторону Node.js, то ясно, что меня привлекла свобода, которая характерна для Node.js-разработки. Экосистема Java превратилась в обузу, а при использовании Node.js ничего такого не ощущается. Конечно, если мне, по работе, придётся писать на Java, я выполню эту задачу.

У каждого приложения имеются собственные нужды. И, безусловно, неправильно всегда и для всего использовать исключительно Node.js только из-за того, что эта платформа кому-то нравится. Выбор того или иного языка или фреймворка должен определяться техническими соображениями. Например, недавно мне пришлось работать с XBRL-документами. Так как лучшие библиотеки для работы с XBRL написаны на Python, для того, чтобы ими пользоваться, надо знать Python. Поэтому, выбирая технологии, нужно здраво оценивать реальные задачи проектов и останавливаться именно на том, что лучше всего подходит для решения этих задач.

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

RUVDS.com

926,00

RUVDS – хостинг VDS/VPS серверов

Поделиться публикацией
Комментарии 30
    +2
    Почему человек из мира Java стал горячим сторонником Node.js и JavaScript?

    Ну стал и стал. Если человеку что-то надоело и захотелось нового, то бесполезно его уговаривать и приводить какие-то доводы типа: есть же более простые фреймворки чем Spring и JavaEE, кроме maven есть еще и gradle и т.д. Хочется «свободы», ну почему нет-то?
      +8
      Честное слово, автор данного опуса некоторые проблемы java с миру по нитке собирал.
      За 10 лет пользования maven мои сборки не падали при его обновлении. npm — может начать падать при обновлении даже на минорную версию, время сборки билда тоже меняется от версии к версии.
      Проблемы с hibernate, spring – несколько дней на то чтобы понять «detached entity passed to persist»? Я уже вижу уровень полнейшего джуна. Любой джавист это видел при первом использовании hibernate и больше обычно осознанно такую ошибку не делает.

      Автор жалуется на скрытие сложных вещей, но сам не способен разобраться в тривиальнейших вещах, которые обычно написаны в начале документации, а уж про stackover&google driven development вообще умолчу.

      Статья из разряда оправдать свою никчемность, нежели привести реальные кейсы, когда node.js действительно помогает что-то решить быстрее.

      Ах да, Eclipse vs Netbeans? Какого года статья? )
        0
        ну так видимо чувак и был джуном в начале карьеры и запомнил какая java «поганая» а js «мощный»
      +5
      Проблема программирования на языке со строгой типизацией столь велика, что программист, практически без вариантов, должен использовать большую сложную IDE.

      Эм… почему? Звучит как-то очень не обосновано. Потому что ide для языков со статической типизацией удобнее, чем для языков с динамической?)

        +8
        Предатель!

        JS — очень хороший язык для сайтиков с объемом не больше 10 тысяч строк кода

        JS — очень плохой язык для больших настоящих проектов.

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

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

        Большие проекты без IDE ты и не попишешь, вообще. Если IDE нет для языка, его просто не используют.

        V8 — куда слабей как виртуальная машина, особенно в части сборки мусора (нет возможности работать с дейтсвительно большими объемами данных, или действительно маленькими объемами оперативной памяти)
          +1
          Навскидку, десять тысяч строк кода для JS уже могут показаться сложными, требующими десятков джаваскриптеров (и тут вступает на сцену «мифический человеко-месяц», и утаскивает в пучину ада). Проект на сто тысяч строк кажется джаваскриптеру чем-то невероятно непредставимо сложным. Проект на десятки миллионов строк кода в JS существовать не может вообще

          Работать с большой JS кодовой базоы помогает модульность, линтеры, строгие рамки в целом. 10к совсем не проблема для одного разработчика, да и 100к тоже, смотря как разработчик. Но я бы не стал писать более-менее серьезные и крупные проекты на JS, TypeScript ведь.
            0

            Действительно, практически все проекты на JS весьма небольшие. С помощью утилиты sloc подсчитал количество строк кода в показавшихся наиболее объёмными:


            Babel — 100K
            Webpack — 63K
            Angular.js — 120K
            React — 85K
            ExtJS — 1M

              0
              И при том они столько всего делают! Может быть, типичный код на Java слишком раздут без необходимости?
              Есть такое высказывание (не помню автора), что в мире не существует задач, которые требуют миллион строк кода (хотя это и не совсем верно, есть же Linux Kernel с ~20 млн.)
          +3
          Потому что этот джавист долго не хотел сам себе признаться что он скрипт-кидди.

          PS только странно что именно просто JavaScript, а не TypeScript.
            +1
            Ну какая в TypeScript свобода? )
              0
              Очень хорошая. Свобода головы от лишней перегрузки, потому что модель данных, сигнатуры и прочее можно один раз выразить в коде, а не каждый раз загружать в голову. Другое дело что не все готовы «выражать в коде», но это приходит с опытом и необходимостью работать над крупными проектами и в командах размером более одного человека.
                +1
                Это шутка была. Человек вырвался из проклятой статической типизации Java и что опять в типизацию что ли?
                  +1
                  Могу предоложить что мнимаю свободу человек ощутил поменяв тип проектов, компанию, возможно степень ответственности, а не просто Java => JS. Потому что ответственно работать с большой кодовой базой на JS и при этом глотать свежий воздух обычно не получается.

                  То есть могу предположить что теперь грубо говоря он может себе позволить работать по методологии «хуяк-хуяк и в продакшен» при этом не беря на себя ответсвенность. Либо проекты небольшие/одноразовые. Если это так, то я бы тоже глотал свежий воздух работая с JS.
            +1
            Программирование для Node.js — это сплошное удовольствие

            Сдается мне он все еще на той стадии, когда «очень приятно, и еще не вылетело исключение из черт знает какой подсистемы»…
              +6
              До сих пор помню ту ярость, когда оказалось, что простейший сервер на node.js возвращающий «Hello, world!» пишется в 5 строчек, а в джаве это здоровенный сервлет + еще томкат развернуть не забудь и деплой собери и web.xml проверь…
              До этого делал собственные мелкие проекты на джаве.
              А еще это исчадие под названием мавен, которое «Мне надо скачать 200 Мб обновлений репозиториев ПРЯМО СЕЙЧАС, поэтому вот тебе стоп зе ворлд, пойди выпей чаю и съешь еще этих мягких французских булок»
                0
                что это за обновления такие? у вас весь проект на снэпшотах?
                  0
                  а в джаве

                  Это не в «джаве», а в JavaEE. На SE тоже можно уложиться тупо кодом:
                  embeddedServer(CIO, 8080) {
                    get("/") { call.respondText("Hello World") }
                  }
                  

                  На Java будет чуть подлинее конечно (нужно класс объявить, public static void main, /etc), но тоже довольно коротко.
                    0

                    справедливости ради — приведённый код это не сколько Java SE, сколько Kotlin over Java SE

                      0
                      Просто у Kotlin так сказать есть «дефолтный сервер» — это ktor. Для чистой Java встаёт вопрос «а что собсно у нас будет запросы обрабатывать?». Вот пример с com.sun.net.httpserver:
                      public class Main {
                          public static void main(String[] args) throws Exception {
                              HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
                              server.createContext("/", (t) => {
                                  String response = "Hello World".getBytes();
                                  t.sendResponseHeaders(200, response.length);
                                  OutputStream os = t.getResponseBody();
                                  os.write(response);
                                  os.close();
                              });
                              server.start();
                          }
                      }
                      


                      Можно наколдовать с более удобным api, взять более удобную библиотеку, /etc…
                  –1
                  Гипероксия вполне способна вскружить голову, да.
                    0
                    И… не буду говорить о Maven. Это — просто кошмарный инструмент.

                    Ну не знаю… Я как поглядел на webpack/gulp/babel/flow/ts/чотамещё у меня возникла только одна мысль — мамочка роди меня обратно.

                    Что сложного в управлении пакетами maven? Аналогично указываешь имя пакета (ну и его группу), version constraints там тоже есть — с этой точки всё аналогично npm/yarn, в чём трудности?
                      0
                      Работая в Sun, я верил в технологию Java. Я делал доклады на JavaONE,...

                      То есть чел реально не фигачил и не поддерживал ни одного проекта с достаточно большой кодовой базой. В лучшем случае делал демки и хелло ворлды для конференций. Поэтому весь остальной опус можно смело смывать в унитаз как малозначимый субъективный опыт.


                      Программы на Java полны шаблонного кода, который скрывает намерения программиста.

                      Пишите проще, вас никто не заставляет писать шаблонно.


                      Разработка с использованием Spring — это, до определённого момента, занятие приятное.

                      Spring — это инструмент. Если не умеете им пользоваться, то есть только два варианта: либо не пользуйтесь, либо учитесь.


                      Платформа Java EE была проектом, созданным, так сказать, «всеобщими усилиями»

                      JEE — это страшный сон для Java родом из конца 90-х, который слава богу скоро сдохнет.


                      Программирование для Node.js — это сплошное удовольствие.

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


                      В JavaScript нет строгой типизации, характерной для Java. Это — благословение и проклятие языка.

                      Нет. Отсутствие типизации — это неизменный атрибут всех write-only language.


                      Обратная сторона строгой типизации Java — это необходимость в постоянном выполнении шаблонных действий. Программист постоянно производит приведение типов или занимается проверкой того, чтобы всё было в точности так, как ожидается.

                      Это вообще пальцем в небо. Программист на Java в первую очередь занимается ДИЗАЙНОМ приложения, когда описыавет интерфейсы, классы и методы. А частое приведение типов говорит как раз о плохом дизайне, и что программисту лучше вообще писать на JavaScript. С "шаблонным кодом" легко справляется любая IDE, а кроме того, с появлением лямбд он свелся к минимуму. "Проверкой как ожидается" занимается не программист, компилятор и IDE, причем прямо в реальном времени.


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

                      Да нифига не легче. Когда вы видите переменную или параметр, который может быть всем чем угодно, и с "емким" однобуквенным именем… Да и писать тоже не легче, именно из-за отсутствия поддержки IDE и выпадающих пропертей объекта. Хорошо, когда знакомы по памяти с библиотекой...


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

                      Да, все очень круто. Мы все помним историю с "left-pad", который ВНЕЗАПНО! сломал тысячи проектов… И одно то, что куча JS-программеров испытывает нужду в стороннем "пакете" длиной 11 строчек, говорит о многом...


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

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

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

                        Всё так (с).
                          +2
                          Если вы, как и автор этой статьи, перешли на JavaScript с какого-то другого языка, или сменили какую-нибудь серверную платформу на Node.js, просим в двух словах об этом рассказать

                          Моя история, когда то начинал на java, когда появился c#, было не сложно освоить и его. Больше 10 лет я писал в основном на этих 2х языках с перевесом в c#. Это были серверные приложения, разброс большой от внутреннего пользования, до сервисов большой корпорации с миллионами клиентов. Клиентские приложения, тоже разброс большой, от вебсайтов, нативных приложений, до мобильных. Освоив какое то направление мне становилось скучно и я смотрел, что еще у нас тут есть. За это время я сделал много курсов повышения квалификации, в том числе и по внутренностям обоих платформ, нашел много неуловимых дедлоков и утечек памяти, подебагил крэш дампы с windbg и другие радости. Я не знаю всего и есть много програмистов, круче меня, но я знаю больше среднестатистического програмиста. Сейчас я fullstack, потому что «если хочешь что бы что то работало хорошо, сделай это сам». Сервер последний год тот самый spring boot, до этого пару лет был nodejs.

                          Итак все началось лет 5 назад, хайп про node js уже давно стоял, но не вызывал у меня ничего кроме рвотных позывов, javascript? Это тот язык, в котором нет типов, один поток и местами очень странное поведение, вы серьезно? Я С трудом заставлял себя писать клиенские скрипты для сайтов, а тут вообще все, да вы с ума сошли. Один такой сумасшедший коллега с моей работы, донимал меня на перекурах с этой нодой и что бы один раз и навсегда заткнуть ему рот, я решил написать версию одного нашего небольшого сервиса написанного на c#, на ноде. Синтетические тесты хорошо, но вот тебе пример из жизни и твоя нода сейчас причмокнет. Нет, нода не обогнала c# но она выдала туже производительность, при том что на c# мы вложили очень много в производительсть, мы отпрофилировали все блокировки, мы уменьшили количество context switch и прочие радости, мы выжали максимум из c# и поверьте мы знали как это сделать. Разумеется для ноды я не пытался делать никаких оптимизаций, зачем мне помогать врагу, что говорит о том, что код написанный програмистом, который не умеет правильно настроить многопоточное приложение на c# имеет большую вероятность, что будет работать хуже чем на ноде. Еще раз, в языках типа java и c#, неправильно написанное многпоточное приложение, будет выдавать худшую производительность, чем однопоточное. При этом если вы просто начнете писать многопоточное приложение без понимания, что там под капотом, вы напишете его неправильно. Конечно оно будет работать, но в разы или даже на порядки медленней чем могло бы. Первый бастион пал.
                          Ок, ну допустим говорил я, а что там с типами, как можно без типов вообще жить, появился typescript, который по началу был не очень, но очень быстро стал вау, это очень редкий случай когда получилось относительно безкомпромиссно. К сожалению мне не удалось убедить всех перейти на него, в большой команде всегда найдется своя баба яга, а тут если не все, то никто, сошлись хотябы на прикручивании babel, тут я начал замечать что мне лень писать типы в серверной части, без них прекрасно можно жить, код проверяется линтером и юниттестами, ошибок в типизации практически нет, если что их легко найти и исправить. Рефакторинг работает. Второй бастион пал незаметно, но сегодня я предпочту писать код в js, чем в java, он намного компактней.
                          Третий бастион, «странное поведение» после того как я разобрался с внутренностями js, а они на порядок проще внутренностей java или c#, все стало на свои места, некоторые спорные моменты можно не использовать.
                          Про spring, сейчас я его использую на сервере, это был не мой выбор, чем он плох, в нем слишком много магии, при этом вы очень быстро столкнётесь с тем, что вы все равно должны знать секреты всех фокусов. Хорошо хоть код открыт и хоть мой сервер достаточно примитивен, мне уже пришлось немного перелопатить сорс код спринга, дебагить это вообще жесть, куча слоев абстракции, что бы понять кто как связан, нужно много времени. Да да, разумеется с момента когда я научился пользоваться гуглом, документацией и стаковерфлоу, уже кто то родился, пошел и закончил школу, лучше способа понять, как оно работает, чем сорс код нет. Но если требование к серверу быть написанным на java другие варианты еще хуже.
                          И это просто мой опыт, никого ни в чем не убеждаю и не навязываю. Универсальной технологии нет, универсальной архитектуры тоже нет, хорошо, что есть многообразие. Всем хороших выходных.
                            0
                            Что же это за пример из жизни, который вы полировали на c#, а на ноде – оно работало сразу без проблем?
                              0
                              Да это не особо важно, сервис получил запрос, обработал, отдал ответ, можете попробовать свой вариант из жизни. Нод выдает высокую производительность. Даже тяжелые вычисления, для которых нод не ремендуют, обычно можно разбить на части.
                                –3
                                Имхо, нода это хорошо, когда вы не пишете никакого кода.

                                Только бойлерплейт в виде анскильных крудов — достал из базки, фильтранул, показал в UI. Забрал из UI команду вида «удали элемент списочка», пропустил сквозь сервер, затолкал базу. Ну и там логика фильтрации-сохранения на сервере — из трех строчек.

                                По сути, всё что нужно от жизни — это быстрый обработчик хттп-запросов, и ура — его за вас уже написали самые наикрутейшие инженеры в мире. Достаточно чтобы этот сервер и ваша БД не тормозила, и приложение тоже не будет тормозить. Это IO-bound процесс, причём вы никак не можете повлиять на качество реализации этого IO, его делают за вас какие-то неизвестные внешние компании.

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

                                Вопросы возникают тогда, когда нужно писать код. Когда вы пишете реально много хитрой умной системной логики. Например, вы пишете базу данных, in-memory data grid, масшатбирующийся на произвольное количество узлов (такой как Apache Ignite). Не используете готовую базу данных, а пишете свою.

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

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

                                Или у вас кластер из сотен микросервисов, и что-то там стало тормозить. Запускаешь — а оно запускается шесть часов вместо расчётных двух. Те же банки, облачные платформы, итп.

                                Удачи вам всё это писать и отлаживать на JS.

                                Хотя я знаю вариант хуже — попробовать использовать для этих задач Golang :)
                            0
                            >findUserByFirstName, учитывая то, что программист не писал код подобного метода
                            можно и руками писать такие методы, кто же его заставлял. можно не пользоваться сложными и автоматизированными фишками.

                            И само сравнение JS vs Java достаточно странное. Это принципиально разные инструменты.
                              0

                              Всегда полезно посмотреть на вещи с другой стороны.

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

                              Самое читаемое