JavaScript как праздник

в 2:40, , рубрики: javascript, node.js

Эта пятничная статья будет ответом на статью JavaScript как явление, в которой некий пользователь (видимо по случаю прошедшего дня ВДВ) решил избить JavaScript. Лично я пишу на JavaScript уже 15 лет, искренне считаю его одним из самых мощных ЯП на сегодняшний день, поэтому не позволю его обижать. В статье будет, по возможности, аргументированная позиция относительно основных тезисов критики. По правде, вышеупомянутая статья не стоит того, чтобы на неё отвечать, но у новичков действительно часто возникают проблемы с JavaScript. Вводит в заблуждение приставка Script и несерьёзный имидж языка, а не деле обнаруживается, что язык применяется от front-end и back-end до дескопных и мобильных приложений, программирования интегральных микросхем, обработки видео и в множестве других сфер. Я давно хотел раскрыть частые заблуждения про JavaScript, а тут как раз появился повод, поэтому welcome под кат.
image

На данный момент JavaScript — самый популярный ЯП на планете. И, как бы я не уважал TypeScript, Java, C#, Go, другие языки — у них нет шансов изменить статус кво. Хотя вероятно они очень хороши для своих задач. Причина по которой JavaScript стал таким популярным (кроме монополии в веб) — его демократичность. Он позволяет программировать и в процедурном стиле, и в объектно-ориентированном и в функциональном. Он накладывает минимальные ограничения на разработчика, позволяя творить любую «глупость», но ирония состоит в том, что то, что является глупостью применимо к одному классу задач, применимо к другому является более чем целесообразным. Легенда гласит, что JavaScript был создан за 2 недели. И опять ирония жизни, в таких условиях можно заложить в язык только самое главное, оставив за бортом всё лишнее, традиционное, «правильное». Первая версия языка получилась очень компактной и лаконичной. Все эти get/set, const и await появились гораздо позже. Изначальные принципы языка были настолько хороши, что 10 лет (с 1999 по 2009) язык прожил вообще без изменений. Конечно к этому были и негативные причины, политика Microsoft и Mozilla, многое другое, но, уверен, не многие из других популярных языков смогли бы пройти такое же испытание и подняться после этого. Просто представьте, что стало бы с TypeScript или Rust после 10 лет отсутствия обновлений. Причина по которой JavaScript выжил очень проста, он решает одну задачу и делает это идеально.

JavaScript не претендует быть синтаксическим сахаром или набором клёвых фич, их программист может написать/подключить и сам. JavaScript скрывает от вас аппаратную часть устройства, даёт возможность делать что угодно с логикой и просто оставляет вас с этим. Хотите eval — пожалуйста, хотите переопределить любой объект — нет проблем, хотите передать в функцию то, что «нельзя» передавать — welcome, потому что это «нельзя» только в вашей голове. Программистам на Go или C# очень сложно понять почему это хорошо. Чтобы не вызвать на себя их хейт, это прекрасные языки, просто другие. Классически в языках ставятся барьеры, не дающие программистам отстрелить себе ногу, это и проверка типов, и различные обязательные best practices, и многое другое. В JavaScript этих барьеров нет, вы в праве стрелять куда угодно, и в 0.01% случаев стрельба в ногу тоже имеет смысл. Это можно сравнить со спортивным автомобилем, у многих языков заблокирована часть функций, а в JavaScript — нет. Если вы водите плохо — возможно это для вас минус, опасности и т.п., но если вы действительно хорошо разбираетесь и в языках, и в архитектуре, и в парадигмах, и умеете всем этим пользоваться — лучше языка чем JavaScript для общих задач вам не найти. Для частных — можно, для общих, универсального — объективно нет. Многие возразят, мол в Java тоже можно создавать словари, аналог JS-объектов, Python и Ruby тоже не типизованные, много где есть и eval и duck typing, но применять это так просто, как в JavaScript не получится нигде. В Java, например, словари это только дополнение, приделанное на типизованную класс-ориентированную основу, а в JavaScript — это основа языка, и создаётся всего двумя символами "{}". Это как если бы в спортивном автомобиле форсаж вызывался не тремя кнопками и рычажком, а одной кнопкой под большим пальцем правой руки. Свобода не просто возможна, она поощряется.

Многих это вводит в ступор, потому что они привыкли, что им не дадут расшибить лоб. Это как переход с Windows на Linux. «Я ввёл sudo rm -Rf / и всё сломалось. Не система, а г… но». С такими рассуждениями путь до мастера будет очень долгим. Порог входа в JavaScript был и остаётся очень низким, это даёт повод многим новичкам ругать то, в чём они не разобрались. Причём у человека может быть 20 лет опыта в Lisp, но по JavaScript он всё равно даже документацию не читал, типа и так умный. Этого достаточно, чтобы писать простые программы, но если человек хочет понять почему true < 2 === true, и почему это правильно и логично — прочитать про преобразования типов must have, а в идеале всю документацию (или хорошую полную книгу), это не долго.

Теперь отвечу на критику по пунктам:

Вопрос 1: однопоточный рантайм

Это очень удобно, не возникает проблем с блокировками и владением объектами и других прелестей многопоточности. Зачем вам нужна многопоточность? Выполнять программу дальше пока ждёте выполнения долгой операции? Колбеки справляются с этим гораздо лучше. NodeJS на одной средней машинке может держать по 100 000 коннектов. Сколько бы их было при замене колбеков на поточный подход? На многопроцессорных машинках js прекрасно параллелится запуском локального кластера. 8 ядер — 8 процессов, 16 ядер — 16 процессов, каждый независим друг от друга и прост внутри. Это реальный пример из практики применения, как главной серверной технологии онлайн игры с 8 миллионами пользователей. Работа с ассинхронностью/потоками это не слабость, а одно из мощнейших преимуществ JavaScript. Это может требовать переучивания и изменения привычек, но поверьте, вы приобретёте очень многое.

Вопрос 2: отсутствие единой системы / стандартов реализации модулей

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

  • официальный, через import
  • и традиционный, через require

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

Вопрос 3: отсутствие единых стандартов структуры проекта (все творят как хотят, в исходниках бывает очень сложно разобраться)

Сочувствую вам с коллегами, которые «творят что хотят». Или коллегам с вами. В веб-разработке есть несколько типовых структур проекта, как правило используется одна из них, но это нигде не постулировано, и каждый действительно волен писать свою программу исходя из своего взгляда на целесообразность. А что вы хотели? Это самый популярный язык на планете, а не какой-то DSL. В разных сферах применения JS разные стандарты, и это правильно. Что касается практики, я, например, прекрасно читаю даже обфусцированный код библиотек, чего и вам желаю. Нарабатывайте опыт и изучайте распространённые паттерны.

Вопрос 4: слабые типы с неявными (и порой довольно странными) преобразованиями

Странными для кого? Программистов Java, C#, PHP, Python, Lisp или Ams? Скажете asm не странный? А Lisp? Мир гораздо богаче вашего любимого языка и то, что для одних странно, для других норма. Посмотрите хотя бы на Haskell с его монадами и функторами (очень мощные штуки, кстати. В JS тоже используются, в jQuery). В институте этому не учили, правда? ООП это только малая часть мира, настолько малая и настолько заезженная, что даже скучно говорить. А типы в JavaScript не слабые, их принципиально нет (кроме примитивных). WeakMap и прочее ввели только для того, чтобы порадовать переселенцев из других языков. Перечитайте про duck typing и научитесь им пользоваться, проблем с типами у вас не возникнет.

Вопрос 5: отсутствие нормальных классов / ООП
Опять же, специально для переселенцев с других языков и для IDE уже довольно давно введены классы. Их поддерживают все основные браузеры, не говорю уже про NodeJS. ООП в JavaScript богаче, чем в большинстве других языков. Можете наследовать через классы, можете через прототипы. Во многих случаях правильного применения JavaScript прототипы оказываются быстрее, удобнее и логичнее, а программа компактнее и более читаемой. Но опять же, этому не учат в институте, а JavaScript сообщество должно потом доказывать, что так тоже можно.

Вопрос 6: отсутствие единого вменяемого и работающего статического анализатора кода (добро пожаловать в чудесный мир глупейших ошибок типа undefined is not a function)

Это общая проблема всех интерпретируемых языков с eval, и отказываться от этой мощи ради возможности ловить 5% самых глупых ошибок — сомнительная идея. А вообще, развивайте дисциплину кода, не всё время за юбкой IDE прятаться. Это не стёб, анализаторы это хорошо, но если для вас проблема такие ошибки — как-то вы не правильно программируете.

Вопрос 7: отсутствие вывода типов в самом языке или в каком-либо инструменте

Это есть, изучайте синтаксис. Один из вариантов, в зависимости от ситуации:

typeof myVar
myVar.constructor

Вопрос 8: этот чудесный контекст this (что это значит this в этом месте кода — объект? функция?)

При правильном использовании проблем с this не возникает. Если вы вызываете функцию как myObj.func(), то можете быть уверены, что this будет равен myObj. При назначении колбеков или передаче функций как параметров, информация о myObj теряется и, если необходимо, следует задавать его явно через bind. Это логично и понятно знающим JS, так как вы, например, можете сделать так myObj2.func = myObj.func, и функция будет методом сразу нескольких сущностей. Не правильно назначать this равным myObj или myObj2, так как они симметричны. Не правильным было бы и использовать лексический контекст, так как это внесёт путаницу в миксины, прототипное наследование и многое другое. Поэтому this в таких случаях равен window или undefined, в зависимости от использования strict mode. Но это не должно вас волновать, и это принципиальный момент. Это один из типичнейших примеров стрельбы себе в ногу. На что вы надеетесь, вызывая this у функции, вызванной без контекста? Явно не на что-то хорошее. Есть много таких примеров, люди складывают массивы с числами, делят получившееся на объект и жалуются, что получают странные результаты. Во-многих странных вещах есть логика, но в особо странных это может быть просто произвол. JavaScript гибкий язык и позволяет вам делать то, с чем с C++ у вас программа даже не скомпилировалась бы, в надежде на то, что вы знаете, что делаете. Не нужно пренебрегать этим доверием и творить не весть что. Если вы хотите результат, то просто используйте this правильным логически обоснованным образом, а фразы а-ля «я воткнул себе нож в горло и у меня 2 раза потекла тёмная кровь, а 2 раза светлая, почему так?» оставьте для холиваров. И не надо хвастаться тем, что вы знаете чему в данной неочевидной ситуации равно this или как складываются несуммируемые типы. Профессионально правильной позицией будет, как ни странно, не знать это и не использовать, так как в разных браузерах и в разном контексте вы можете получить разный результат. Но JavaScript всё равно позволяет вам, если вы хотите это использовать, например для того, чтобы отличить PhantomJS c поддельным User-agent от настоящего Chrome — дорога открыта.

Вопрос 9: абсолютно дурацкая реализация pattern matching ( паттерн матчишь пустой список / объект — без проблем, извлекаешь оттуда undefined, ты же именно это имел ввиду, да? ) и здесь опять привет cannot read property foo of undefined

Два совета:

  1. не делайте логических ошибок. Ваши ошибки — не вина языка
  2. если хотите сделать регулярные выражения удобнее — используйте или напишите библиотеки. Похоже на то, что ругать C# за то, что он с Facebook не интегрирован. Руки есть, голова есть, напишите что хотите, или возьмите одну из многочисленных библиотек.

Вопрос 10: отсутствие единой технологии работы с асинхронным кодом — колбэки, примисы, фьючерсы, async (если в проекте более одной зависимости из npm то гарантированно в коде появятся все из них вперемешку)

И колбеки, и промисы, и async/await — нативные, поэтому код они не утяжеляют. Не знаю, что вы называете фьючерсами, я этим не торгую. И это прекрасно, что у вас есть выбор, что использовать. Колбэки это основа всего и ни промисы ни async/await без них не будут работать, это базовый кирпичик языка. Промисы и async/await прекрасно совместимы друг с другом и вы легко можете использовать await с любой функцией, возвращающей промис. Если же вы имели ввиду популярную библиотеку async для node, то сочувствую, ваши знания JS устарели. Библиотека хорошая, но используется всё реже ввиду появления вышеуказанного функционала в ES6. Но подтянуть её в зависимости на ноде тоже не страшно, серверные зависимости не отдаются пользователю и легко бекапятся, на случай удаления с npm (на моей практике такого не было ни разу). А ещё есть Fibers, и Sync, и много классных инструментов, используемых по назначению врача. Выбирайте тот, что больше подходит под конкретные задачи, и не жалуйтесь, что их слишком много.

Вопрос 11: const ( который на самом деле НЕ const )

Не знаю, почему вы так решили. Моя простая проверка в консоли показала обратное:

const a = 5
const a = 4
VM1825:1 Uncaught SyntaxError: Identifier 'a' has already been declared
    at <anonymous>:1:1

Но на самом деле так это или нет — не важно, const создан не для вас, а для интерпретатора. А вам не нужно без глубокого знания языка менять const. Тем более, что на разных движках/браузерах/устройствах этот функционал может работать по разному, и вполне возможно найти какой-нибудь холодильник, программируемый на диалекте JavaScript, в котором случатся ваши самые страшные кошмары. Язык то открытый и версий реализации великое множество. В браузерах данная ошибка не подтверждена.

Вопрос 12: абсолютно безумный npm, с пакетами качества «братишка я тебе покушать принёс»

Плата за свободу творчества и отсутствие премодерации. Проблема не большая, просто всегда смотрите на число скачиваний или звёзд на github. Качество топовых пакетов очень высокое. Мало популярные тоже бывают неплохие, бывают и такие, как вы нашли. Ответственность за используемые зависимости полностью на вас. Это открытое сообщество и никто вам жёванную пищу в клюв класть не будет. На мой взгляд, эта ситуация лучше, чем в решениях от одного поставщика/судьи, так как у библиотек сотни тысяч вендоров и тысячи из них очень достойные. Если хотите однообразия — посмотрите на решения, например, от Sencha. Они платные, но про них были вполне неплохие отзывы. В npm тысячи классных библиотек, но вам надо было найти плохую и выстрелить себе в ногу. Стреляйте, кто же вам запретит.

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

Автор: Александр

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js