Способы писать лучше на JavaScript
Не так много людей, которые рассказывают о способах улучшения JavaScript. Вот некоторые из основных методов, которые я использую, чтобы писать более качественный JS код.
Используйте TypeScript
Первое, что вы можете сделать для улучшения своего JS, - это не писать на JS. TypeScript (TS) - это "компилируемый" JS (все, что выполняется в JS, выполняется и в TS). TS добавляет всеобъемлющую необязательную систему типизации поверх ванильного JS. Долгое время поддержка TS в экосистеме была не очень хорошей, не было и смысла смотреть в эту сторону. К счастью, те времена давно позади, и большинство фреймворков поддерживают TS из коробки. Теперь, когда мы все одинаково понимаем, что такое TS, давайте поговорим о том, почему вы захотите его использовать.
TypeScript обеспечивает "безопасность типов".
Безопасность типов описывает процесс, в котором компилятор проверяет, что все типы используются должным образом в коде. Другими словами, если вы создадите функцию foo, которая принимает число:
function foo(someNum: number): number { return someNum + 5; }
Эта функция foo должна вызываться только с числом:
хорошо
console.log(foo(2)); // выводит "7"
плохо
console.log(foo("two")); // неправильный код TS
Кроме накладных расходов на добавление типов в ваш код, у соблюдения безопасности типов нет никаких отрицательных сторон. С другой стороны, преимущества слишком велики, чтобы их игнорировать. Безопасность типов обеспечивает дополнительный уровень защиты от распространенных ошибок/багов, что является благословением для такого языка, как JS.
Типы Typescript делают возможным рефакторинг больших приложений.
Рефакторинг большого JS-приложения может стать настоящим кошмаром. Большая часть трудностей при рефакторинге JS связана с тем, что в нем не применяются сигнатуры функций. Это означает, что функция никогда не может быть использована "не по назначению". Например, если у меня есть функция myAPI, которая используется 1000 различными сервисами:
function myAPI(someNum, someString) { if (someNum > 0) { leakCredentials(); } else { console.log(someString); } }
и я немного изменил сигнатуру вызова:
function myAPI(someString, someNum) { if (someNum > 0) { leakCredentials(); } else { console.log(someString); } }
Я должен быть на 100% уверен, что в каждом месте, где используется эта функция (1000 мест), я правильно обновил ее использование. Если я пропущу хотя бы одно место, мои учетные данные могут утечь. Вот тот же сценарий с TS:
до
function myAPITS(someNum: number, someString: string) { ... }
после
function myAPITS(someString: string, someNum: number) { ... }
Как вы можете видеть, функция myAPITS претерпела те же изменения, что и ее аналог на JavaScript. Но вместо того, чтобы привести к корректному JavaScript, этот код приводит к некорректному TypeScript, поскольку 1000 мест, где он используется, теперь предоставляют неправильные типы. А благодаря "безопасности типов", о которой мы говорили ранее, эти 1000 случаев будут блокировать компиляцию, и ваши учетные данные не утекут (что всегда приятно).
TypeScript упрощает коммуникацию в командной архитектуре.
Когда TS настроен правильно, будет трудно писать код без предварительного определения интерфейсов и классов. Это также обеспечивает возможность обмена краткими, коммуникабельными предложениями по архитектуре. До появления TS существовали другие решения этой проблемы, но ни одно из них не решало ее нативно и заставляло нас делать дополнительную работу. Например, если я хочу предложить новый тип запроса для моего бэкенда, я могу отправить коллеге по команде с помощью TS следующее.
interface BasicRequest { body: Buffer; headers: { [header: string]: string | string[] | undefined; }; secret: Shhh; }
Мне уже приходилось писать код, но теперь я могу поделиться своим постепенным прогрессом и получить обратную связь без дополнительных затрат времени. Я не знаю, является ли TS по своей природе менее "глючным", чем JS. Я твердо убежден, что если заставлять разработчиков сначала определять интерфейсы и API, то в результате получается более качественный код.
В целом, TS превратился в зрелую и более предсказуемую альтернативу ванильному JS. Определенно, все еще необходимо быть уверенным в том, что тебе комфортно работать с ванильным JS, но большинство новых проектов, которые я начинаю, с самого начала работают на TS.
Используйте современные фичи
JavaScript - один из самых популярных (если не самый) языков программирования в мире. В последнее время в JS было внесено много изменений и дополнений (технически это ECMAScript), которые коренным образом изменили опыт разработчиков.
async
и await
Долгое время асинхронные, управляемые событиями колбэки были неизбежной частью разработки JS:
Традиционный callback
makeHttpRequest('google.com', function (err, result) { if (err) { console.log('Oh boy, an error'); } else { console.log(result); } });
Я не буду тратить время на объяснение того, почему это проблематично, было сказано уже много слов ранее. Чтобы решить проблему с колбэками, в JS была добавлена новая концепция "promise ". Promise позволяют вам писать асинхронную логику, избегая при этом проблем с вложенностью, из-за которой весь код превращался в callback hell (ад коллбэков).
Promise
makeHttpRequest('google.com').then(function (result) { console.log(result); }).catch(function (err) { console.log('Oh boy, an error'); });
Самым большим преимуществом Promise перед колбэками является читаемость кода и построение в цепочку.
Хотя Promise прекрасны, они все же оставляют желать лучшего. В конце концов, написание Promise по-прежнему не ощущался «нативным». Чтобы исправить это, комитет ECMAScript решил добавить новый метод использования обещаний, async и await:
async
and await
try { const result = await makeHttpRequest('google.com'); console.log(result); } catch (err) { console.log('Oh boy, an error'); }
Единственная оговорка - все, что вы ожидаем (await), должно быть объявлено как async:
async function makeHttpRequest(url) { // ... }
Также можно ожидать Promise напрямую, поскольку асинхронная функция - это просто обертка Promise. Это также означает, что код async/await и код Promise функционально эквивалентны. Поэтому не стесняйтесь использовать async/await, этот функционал прекрасен.
let
и const
На протяжении большей части существования JS существовал только один квалификатор области видимости переменной var
. У var есть несколько довольно уникальных / интересных правил относительно того, как он обрабатывает область видимости. Поведение var в области видимости непоследовательно и сбивает с толку и приводило к неожиданному поведению и, следовательно, к ошибкам на протяжении всего времени существования JS. Но в ES6 есть альтернатива var - это const и let. Практически нет необходимости больше использовать var, да и не надо. Любая логика, использующая var, всегда может быть преобразована в эквивалентный код на основе const и let.
Что касается того, когда использовать const и let, я всегда начинаю с объявления всех переменных как const. const является гораздо более строгим и «неизменным», что обычно приводит к лучшему коду. Не существует тонны «реальных сценариев», в которых необходимо использовать let, я бы сказал, только 1/20 всех переменных, которые я объявляю, объявляются как let. Остальные все const.
Я сказал, что const является «неизменным», потому что она не работает так же, как const в C / C ++. Что const означает для среды выполнения JavaScript, так это то, что ссылка на эту переменную const никогда не изменится. Это не означает, что содержимое, хранящееся по этой ссылке, никогда не изменится. Для примитивных типов (число, логическое значение и т. д.) const действительно не изменит своего значения (потому что это единственный адрес памяти). Но для всех объектов (массивов, функций и т.д.) const не гарантирует неизменяемости.
Стрелочные функции (Arrow =>
Functions)
Стрелочные функции - это краткий метод объявления анонимных функций в JS. Анонимные функции, описывают функции, которые явно не названы. Обычно анонимные функции передаются как обратный вызов или обработчик события.
ванильная анонимная функция
someMethod(1, function () { // не имеет имени console.log('called'); });
По большей части, в этом стиле нет ничего плохого. Ванильные анонимные функции ведут себя "интересно" в отношении контекста, что может, да и приводит к множеству неожиданных ошибок. Однако, больше не нужно об этом беспокоиться благодаря стрелочным функциям. Вот тот же код, реализованный с помощью стрелочной функции:
анонимная стрелочная функция
someMethod(1, () => { // не имеет имени console.log('called'); });
Помимо большей лаконичности, стрелочные функции также имеют гораздо более практичное поведение контекста. Стрелочная функция наследует его из области, в которой они были определены, то есть this
внутри будет тем же самым, что и был вне этой функции во время ее объявления (создания).
В некоторых случаях стрелочные функции могут быть еще более краткими:
const added = [0, 1, 2, 3, 4].map(item => item + 1); console.log(added) // выводит "[1, 2, 3, 4, 5]"
Стрелочные функции, расположенные в одной строке, включают неявный оператор возврата. Нет необходимости в скобках или точках с запятой для функций однострочной стрелки.
Я хочу прояснить это. Это не ситуация с var, все еще есть допустимые варианты использования ванильных анонимных функций (в частности, методов класса). При этом я обнаружил, что если вы всегда по умолчанию используете стрелочную функцию, вы в конечном итоге выполняете гораздо меньше отладки, чем при использовании ванильных анонимных функций.
Как обычно, документы Mozilla - лучший ресурс.
Spread оператор...
Извлечение пар ключ/значение из одного объекта и добавление их в качестве дочерних элементов другого объекта - очень распространенный сценарий. Исторически существовало несколько способов добиться этого, но все эти методы были довольно громоздкими:
const obj1 = { dog: 'woof' }; const obj2 = { cat: 'meow' }; const merged = Object.assign({}, obj1, obj2); console.log(merged) // выводит { dog: 'woof', cat: 'meow' }
Этот шаблон невероятно распространен, поэтому описанный выше подход быстро становится утомительным. Благодаря оператору "spread" больше никогда не придется его использовать:
const obj1 = { dog: 'woof' }; const obj2 = { cat: 'meow' }; console.log({ ...obj1, ...obj2 }); // выводит { dog: 'woof', cat: 'meow' }
Самое замечательное, что это также легко работает с массивами:
const arr1 = [1, 2]; const arr2 = [3, 4]; console.log([ ...arr1, ...arr2 ]); // выводит [1, 2, 3, 4]
Возможно, это не самая важная и последняя функция JS, но она одна из моих любимых.
Шаблонные литералы (шаблонные строки)
Строки - одна из самых распространенных конструкций программирования. Вот почему так досадно, что нативное объявление строк до сих пор плохо поддерживается во многих языках. Долгое время JS был в семействе "дрянных строк". Но добавление шаблонных литералов вывело JS в отдельную категорию. Шаблонные литералы нативно и удобно решают две самые большие проблемы с написанием строк: добавление динамического содержимого и написание строк, охватывающих несколько строк:
const tgChan = 'https://t.me/ZadachiFronta'; const myString = `Подписывайся на канал ${name}`;
Я думаю, что код говорит сам за себя. Какая удивительная реализация.
Деструктуризация объектов
Деструктуризация объекта - это способ извлечения значений из коллекции данных (объект, массив и т.д.) без необходимости итерации данных или явного доступа к их ключам:
старый способ
function animalParty(dogSound, catSound) {} const myDict = { dog: 'woof', cat: 'meow', }; animalParty(myDict.dog, myDict.cat);
деструктуризация
function animalParty(dogSound, catSound) {} const myDict = { dog: 'woof', cat: 'meow', }; const { dog, cat } = myDict; animalParty(dog, cat);
Но подождите, это еще не все. Вы также можете определить деструктуризацию в сигнатуре функции:
деструктуризация 2
function animalParty({ dog, cat }) {} const myDict = { dog: 'woof', cat: 'meow', }; animalParty(myDict);
Он также работает с массивами:
деструктуризация 3
[a, b] = [10, 20]; console.log(a); // выводит 10
Существует масса других современных функций, которые вам следует использовать. Вот несколько других, которые мне особенно запомнились:
Всегда учитывайте, что ваша система распределенная
При написании распараллеленных приложений ваша цель - оптимизировать объем работы, выполняемой за один раз. Если у вас есть 4 доступных ядра, а ваш код может использовать только одно ядро, 75% вашего потенциала расходуется впустую. Это означает, что блокирующие, синхронные операции - главный враг параллельных вычислений. Но учитывая, что JS - это однопоточный язык, на нескольких ядрах ничего не работает. Так в чем же смысл?
JS - однопоточный, но не однофайловый (как линейки в школе). Даже если он не параллельный, он все равно "одновременный". Отправка HTTP-запроса может занять несколько секунд или даже минут, если бы JS останавливал выполнение кода до тех пор, пока не придет ответ на запрос, язык был бы непригоден для использования.
JavaScript решает эту проблему с помощью цикла событий (event loop). Цикл перебирает зарегистрированные события и выполняет их на основе внутренней логики планирования/приоритизации. Именно это позволяет отправлять 1000 "одновременных" HTTP-запросов или читать несколько файлов с диска в "одно и то же время".
map
const urls = ['google.com', 'yahoo.com', 'aol.com', 'netscape.com']; const resultingPromises = urls.map((url) => makeHttpRequest(url)); const results = await Promise.all(resultingPromises);
forEach
// такой вариант тоже не блокирует код urls.forEach(async (url) => { try { await makHttpRequest(url); } catch (err) { console.log(`${err} bad practice`); } });
Существуют и другие допустимые варианты асинхронного выполнения, помимо map
и forEach
, например for-await-of
.
Линтеры для кода и соблюдение стиля
Код без единого стиля (внешнего вида и ощущения), его невероятно трудно читать и понимать. Поэтому критически важным аспектом написания высококлассного кода на любом языке является наличие последовательного и разумного стиля. Из-за широты экосистемы JS существует множество вариантов организации и стиля кода. Очень важно использовать линтер (любой из них), потому что каждый пишет код так, как привык, а в проектах код должен выглядеть так, как будто его писал один человек, без линтера достичь такого результата тяжело.
Я вижу, что многие люди спрашивают, что лучше использовать - eslint или prettier. На мой взгляд, они служат совершенно разным целям, и поэтому должны использоваться вместе. Eslint - это традиционный "линтер", в большинстве случаев он выявляет проблемы в вашем коде, которые имеют меньше отношения к стилю, а больше к корректности. Например, я использую eslint с правилами AirBNB. При такой конфигурации следующий код заставит linter потерпеть неудачу:
var fooVar = 3; // правила airbnb запрещают "var"
Должно быть очевидно, как eslint повышает ценность вашего цикла разработки. По сути, он следит за тем, чтобы вы следовали правилам относительно того, что "является" и "не является" хорошей практикой. В связи с этим, линтеры по своей сути являются рекомендациями. Как и любая рекомендация, воспринимайте ее с долей сомнения, линтеры могут ошибаться.
Prettier - это утилита для форматирования кода. Его меньше волнует "правильность кода", но больше единообразие и последовательность. Prettier не будет жаловаться на использование var, но он автоматически поставит на свои места все скобки в вашем коде. В своем личном процессе разработки я всегда запускаю Prettier как последний шаг перед отправкой кода в Git. Во многих случаях даже имеет смысл запускать Prettier автоматически при каждом коммите в репозиторий. Это гарантирует, что весь код, поступающий в систему контроля версий, имеет согласованный стиль и структуру.
Протестируйте свой код
Написание тестов - это косвенный, но невероятно эффективный метод улучшения JS-кода, который вы пишете. Я рекомендую освоить широкий спектр инструментов тестирования. Ваши потребности в тестировании будут разными, и нет ни одного инструмента, который мог бы справиться со всеми задачами. В экосистеме JS существует масса хорошо зарекомендовавших себя инструментов тестирования, поэтому выбор инструментов в основном сводится к личному вкусу. Как всегда, думайте сами.
AvaJS
Драйверы тестирования - это просто фреймворки, которые предоставляют структуру и утилиты на очень высоком уровне. Они часто используются в сочетании с другими, специфическими инструментами тестирования, которые зависят от ваших потребностей в тестировании.
Ava - это баланс выразительности и лаконичности. Мне очень нравится параллельная и изолированная архитектура Ava. Тесты, которые выполняются быстрее, экономят время разработчиков и деньги компаний. Ava может похвастаться тонной приятных функций, например, встроенные утверждения (assert), при этом умудряясь оставаться очень минимальной.
Альтернативы: Jest, Mocha, Jasmine
SinonJS
Эта библиотека дает нам "аналитику функций", например, сколько раз была вызвана функция, для чего она была вызвана, и другие важные данные.
Sinon - это библиотека, которая делает много вещей, но только некоторые из них очень хорошо. Набор функций богат, синтаксис лаконичен.
Альтернативы: testdouble
Nock
HTTP mocking - это процесс имитации некоторой части процесса http-запроса, чтобы тестировщик мог внедрить пользовательскую логику для имитации поведения сервера.
Http mocking может быть настоящим мучением, но nock делает его менее болезненным. Nock напрямую переопределяет встроенную функцию request в nodejs и перехватывает исходящие http-запросы. Это, в свою очередь, дает вам полный контроль над ответом.
Альтернативы: Я не знаю ни одной :(
Selenium
Selenium - это самый популярный вариант автоматизации веб-процессов, у него огромное сообщество и набор онлайн-ресурсов. К сожалению, кривая обучения довольно крутая, и требуется большое количество внешних библиотек. Тем не менее, это единственный действительно бесплатный вариант, поэтому, если вы не занимаетесь веб-автоматизацией корпоративного уровня, Selenium подойдет для этой цели.
Две интересные детали JS
- Очень редко следует использовать
null
- Числа и их расчеты в JavaScript могут давать не точные результаты
- Всегда используйте параметр radix (второй аргумент) с функцией
parseInt
Источник: https://dev.to/taillogs/practical-ways-to-write-better-javascript-26d4