Советы по улучшению написания кода на JavaScript

  • 11 октября, 16:26
  • 4148
  • 0

Вот некоторые советы, которые помогут  улучшить ваш JS.

Используйте TypeScript

Первое, что вы можете сделать, чтобы улучшить свой JS, - это не писать JS. Для непосвященных TypeScript (TS) - это «компилированный»  JS (все, что выполняется в JS, выполняется в TS). Код на TypeScript компилируется в JS и подходит для разработки любых проектов под любые браузеры — тем более что можно выбрать версию JS, в которую будет компилироваться код 

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

 

 Почему нужно использовать TS:

  1. TypeScript обеспечивает «безопасность типов».

Безопасность типов описывает процесс, в котором компилятор проверяет, что все типы используются «законным» способом во всем фрагменте кода. Другими словами, если вы создаете функцию, fooкоторая принимает число:

function foo(someNum: number): number {  return someNum + 5;
}

Эту fooфункцию следует вызывать только с числом:

хороший пример:

console.log(foo(2)); // prints "7"

так не делайте:

console.log(foo("two")); // invalid TS code

Помимо  расходов на добавление типов в ваш код, у обеспечения безопасности типов нет никаких недостатков. С другой стороны, выгода слишком велика, чтобы ее игнорировать. Безопасность типов обеспечивает дополнительный уровень защиты от распространенных ошибок, что является благом для такого  языка, как JS.

  1. Типы машинописных текстов делают возможным рефакторинг более крупных приложений.

Рефакторинг большого JS-приложения может стать настоящим кошмаром. Большая часть проблем с рефакторингом 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 мест), я правильно обновляю использование. Если я хотя бы пропущу 1, мои учетные данные могут просочиться. Вот такой же сценарий с TS:

до

function myAPITS(someNum: number, someString: string) { ... }

после

function myAPITS(someString: string, someNum: number) { ... }

Как видите, myAPITSфункция претерпела те же изменения, что и ее аналог в JavaScript. Но вместо того, чтобы приводить к правильному JavaScript, этот код приводит к недопустимому TypeScript, поскольку тысячи мест, где он используется, теперь предоставляют неправильные типы. И из-за «безопасности типов», которые мы обсуждали ранее, эти 1000 случаев заблокируют компиляцию, и ваши учетные данные не будут пропущены (это всегда приятно).

  1. TypeScript упрощает общение в командной архитектуре.

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

interface BasicRequest {  body: Buffer;  headers: { [header: string]: string | string[] | undefined; };  secret: Shhh;
}

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

Используйте современные функции

JavaScript - один из самых популярных (если не самый) языков программирования в мире. В последнее время в JS было внесено много изменений и дополнений ( технически это ECMAScript), которые коренным образом изменили опыт разработчиков. 

Долгое время асинхронные обратные вызовы, управляемые событиями, были неизбежной частью разработки JS:

традиционный обратный  вызов

makeHttpRequest('google.com', function (err, result) {  if (err) {    console.log('Oh boy, an error');  } else {    console.log(result);  }
});

Чтобы решить проблему с обратными вызовами, в JS была добавлена новая концепция «Обещания» (promise) . Обещания позволяют писать асинхронную логику, избегая при этом проблем вложенности, которые ранее мешали коду, основанному на обратном вызове.

Обещания (promise) 

makeHttpRequest('google.com').then(function (result) {  console.log(result);
}).catch(function (err) {  console.log('Oh boy, an error');
});

Самым большим преимуществом Promises перед обратными вызовами является удобочитаемость и цепочность.

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

async а также await

try {  const result = await makeHttpRequest('google.com');  console.log(result);
} catch (err) {  console.log('Oh boy, an error');
}

требуемое определение makeHttpRequest в предыдущем примере

async function makeHttpRequest(url) {  // ...
}

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

let и const

На протяжении большей части существования JS существовал только один квалификатор области видимости переменной var. varимеет несколько довольно уникальных / интересных правил в отношении того, как он обрабатывает область видимости. Поведение области видимости varявляется непоследовательным и запутанным и привело к неожиданному поведению и, следовательно, к ошибкам на протяжении всего времени существования JS. Но что касается ES6, есть альтернатива var, constи let. Практически нет необходимости в использовании var. Любую логику, которая использует var, всегда можно преобразовать в эквивалентный constи letоснованный на ней код.

Что касается того, когда использовать constvs let, начинайте с объявления всего const. constявляется гораздо более строгим и «неизменным», что обычно приводит к лучшему коду. Не существует тонны «реальных сценариев», где let необходимо использование. .

Стрелочные =>функции

Стрелочные функции - это краткий метод объявления анонимных функций в JS. Анонимные функции, описывают функции, которые явно не названы. Обычно анонимные функции передаются как обратный вызов или обработчик события.

ванильная анонимная функция

someMethod(1, function () { // has no name  console.log('called');
});

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

анонимная стрелочная функция

someMethod(1, () => { // has no name  console.log('called');
});

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

В некоторых случаях стрелочные функции могут быть еще более краткими:

const added = [0, 1, 2, 3, 4].map((item) => item + 1);
console.log(added) // prints "[1, 2, 3, 4, 5]"

Стрелочные функции, расположенные в одной строке, включают неявный returnоператор. Нет необходимости в скобках или точках с запятой для функций однострочной стрелки.

Это не та varситуация, все еще есть допустимые варианты использования ванильных анонимных функций (в частности, методов класса). При этом ,  если вы всегда по умолчанию используете стрелочную функцию, вы в конечном итоге делаете намного меньше отладки, чем по умолчанию ванильные анонимные функции.

Оператор распространения ...

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

const obj1 = { dog: 'woof' };
const obj2 = { cat: 'meow' };
const merged = Object.assign({}, obj1, obj2);
console.log(merged) // prints { dog: 'woof', cat: 'meow' }

Этот шаблон невероятно распространен, поэтому описанный выше подход быстро становится утомительным. Благодаря «оператору распространения» его больше никогда не нужно использовать:

const obj1 = { dog: 'woof' };
const obj2 = { cat: 'meow' };
console.log({ ...obj1, ...obj2 }); // prints { dog: 'woof', cat: 'meow' }

Самое замечательное, что это также легко работает с массивами:

const arr1 = [1, 2];
const arr2 = [3, 4];
console.log([ ...arr1, ...arr2 ]); // prints [1, 2, 3, 4]

Шаблонные литералы (шаблонные строки)

Строки - одна из наиболее распространенных программных конструкций. Вот почему так неловко, что нативное объявление строк все еще плохо поддерживается на многих языках. 

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

const name = 'Ryland';
const helloString =
`Hello ${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); // prints 10

Всегда предполагайте, что ваша система распределена

При написании параллельных приложений ваша цель - оптимизировать объем работы, которую вы выполняете за один раз. Если у вас есть 4 доступных ядра, а ваш код может использовать только одно ядро, 75% вашего потенциала тратится впустую. Это означает, что блокирующие синхронные операции - главный враг параллельных вычислений. Но, учитывая, что JS - это однопоточный язык, на нескольких ядрах он не работает. Так в чем смысл?

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

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

let sum = 0;
const myArray = [1, 2, 3, 4, 5, ... 99, 100];
for (let i = 0; i < myArray.length; i += 1) {  sum += myArray[i];
}

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

let runningTotal = 0;
for (let i = 0; i < myArray.length; i += 1) {  if (i === 50 && runningTotal > 50) {    runningTotal = 0;  }  runningTotal += Math.random() + runningTotal;
}

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

map 

// in decreasing relevancy :0
const urls = ['google.com', 'yahoo.com', 'aol.com', 'netscape.com'];
const resultingPromises = urls.map((url) => makHttpRequest(url));
const results = await Promise.all(resultingPromises);

map with index 

// in decreasing relevancy :0
const urls = ['google.com', 'yahoo.com', 'aol.com', 'netscape.com'];
const resultingPromises = urls.map((url, index) => makHttpRequest(url, index));
const results = await Promise.all(resultingPromises);

forEach

const urls = ['google.com', 'yahoo.com', 'aol.com', 'netscape.com'];
// note this is non blocking
urls.forEach(async (url) => {  try {    await makHttpRequest(url);  } catch (err) {    console.log(`${err} bad practice`); }
});

Вместо того, чтобы выполнять каждую «итерацию» по порядку (последовательно), такие конструкции, как mapберут все элементы и отправляют их как отдельные события в определяемую пользователем функцию карты. Это напрямую сообщает среде выполнения, что отдельные «итерации» не связаны друг с другом или не зависят друг от друга, что позволяет им выполняться одновременно. Во многих случаях цикл for будет столь же производительным (а может быть, и более) по сравнению с циклом mapили forEach. Потеря нескольких циклов сейчас стоит преимущества использования четко определенного API. Таким образом, любые будущие улучшения реализации этих шаблонов доступа к данным принесут пользу вашему коду.Цикл for слишком универсален, чтобы иметь значимую оптимизацию для того же шаблона.

Существуют и другие допустимые параметры async помимо mapи forEach, например for-await-of.

Соблюдайте стиль

Код без единого стиля (внешнего вида) невероятно труден для чтения и понимания. Следовательно, критически важным аспектом написания высококачественного кода на любом языке является наличие последовательного и разумного стиля. Из-за широты экосистемы JS существует много вариантов линтеров и особенностей стиля. 

Многие люди спрашивают, следует ли им использовать eslint или prettier . Они служат совершенно разным целям, поэтому их следует использовать вместе. Eslint - это традиционный «линтер», в большинстве случаев он выявляет проблемы с вашим кодом, которые не столько связаны со стилем, сколько с правильностью. Например, следующий код приведет к сбою линтера:

var fooVar = 3; // airbnb rules forebid "var"

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

Проверьте свой код

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

AvaJS на Github

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

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

Альтернативы: Jest, Mocha, Jasmine.

Sinon на Github

Шпионы предоставляют нам «функциональную аналитику», например, сколько раз вызывалась функция, чем они вызывались, и другие полезные данные.

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

Альтернативы: testdouble

Веб-автоматизация - Selenium

Selenium на Github

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


0 комментариев
Сортировка:
Добавить комментарий

IT Новости

Смотреть все