Как написать лучший и безопасный код JavaScript

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

Возьмем один пример.

const express = require("express")
const app = express()
app.get("/", (_, res) => res.end("OK"))
app.listen(80)

Приведенный выше код представляет собой пример кода, написанного на nodejs на экспресс-платформе. В этом коде я пытаюсь запустить сервер на 80-м порту.

Вопросы:

  1. Что делать, если мы знаем, что порт 80 уже занят каким-то другим приложением, и пытаемся запустить приведенный выше код.
  2. Как мы узнаем, что произойдет?
  3. Запустится или сломается с какой-то ошибкой?

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

ECMA, не упоминается конкретная спецификация / стандарт. Однако сообщество JavaScript следует определенным правилам кодирования.

1. Типы ошибок

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

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

Пример:

console.log(Number(10).toPrecision(200))

Если мы запустим приведенный выше код, он выдаст RangeError.

RangeError: toPrecision() argument must be between 1 and 100

2. Как справиться с ошибкой

В зависимости от характера вызова API (метода) sync / async, ошибка может обрабатываться по-разному.

2.1 Синхронный

try-catch: вы можете использовать блок try-catch для обработки синхронной ошибки.

try {
  console.log(Number(10).toPrecision(200));
} catch (error) {
  // RangeError: toPrecision() argument must be between 1 and 100
  console.log(error instanceof RangeError); // true
}

Если вы не хотите обнаруживать ошибку и выполнять какие-либо операции. В более новой версии компилятора JavaScript это возможно.

try {
  console.log(Number(10).toPrecision(200));
} catch {}

Если вы хотите выполнить какую-либо операцию по умолчанию при ошибке, вы можете использовать наконец блок после блока catch.

let average;
try {
  average = getAverage(); // Sum function does not exits.
} catch {
} finally {
  average = 0;
}
console.log(`Average is ${average}`);
// Average is 0

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

2.2 Асинхронный

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

Чтобы получить результат асинхронного вызова, мы используем либо объект обратного вызова, либо обещание.

Обработка ошибок в API асинхронного обратного вызова (данные об ошибке обратного вызова): базовый JavaScript базового браузера имеет очень ограниченные асинхронные API. Вы можете создать асинхронную функцию с помощью API-интерфейсов таймера, например setTimeout и setInterval. Вы также можете запросить сервер с помощью вызова AJAX (используя выборку). setTimeout и setInterval не вызывают таких ошибок, которые можно обработать. А fetch - это асинхронный вызов на основе обещаний (мы узнаем позже, как обрабатывать ошибки, связанные с обещаниями). Однако в nodejs есть множество стандартов и сторонних API, что вызывает ошибку. Как и в нашем первом примере express.js.

const express = require("express");
const app = express();
app.get("/", (_, res) => res.end("OK"));
const server = app.listen(80);
server.on("error", function handleListen(error) {
  console.log(error);
});

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

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

Увидев этот шаблон, мы можем обработать ошибку, как указано ниже.

fs.readFile('a file that does not exist', (err, data) => {
  if (err) {
    console.error('There was an error reading the file!', err);
    return;
  }
  // Otherwise handle the data
});

Примечание. Вы не можете обработать ошибку асинхронного обратного вызова в блоке try-catch. Однако есть исключение. Последняя версия ECMA Script, использующая async-await, теперь может обрабатывать ошибку в try-catch. Мы узнаем об этом позже.

// This will not work
try {
  app.listen(80);
} catch (error) {
  // never called
  console.error(error)
}

Обработка ошибок в API на основе обещаний (обещание-затем-улов): после ES5 в JavaScript появился новый шаблон проектирования для обработки обратного вызова для асинхронного API. Это шаблон дизайна обещания. Это решает предыдущую проблему ад обратного вызова.

const promise = new Promise((response, reject) => {
  // some async code here
});
promise
  .then(function onSuccess(data) {
    console.log("SUCCESS");
  })
  .catch(function onError(err) {
    console.error(err);
  });

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

Обработка ошибок в API на основе обещаний с использованием async-await (try-catch-await): обещание намного чище, чем этот обратный вызов. Однако очень сложно понять поток в большой кодовой базе. В последней версии ECMA Script добавлено async-await. Используя async-await, мы можем синхронно писать асинхронный код.

async function main() {
  try {
    await promise1;
    const data = await promise2;
  } catch (error) {
    console.log(error); // SOME ERROR
  }
}
main();

Используя try-catch-await, вы можете обрабатывать несколько ошибок в одном блоке, что было невозможно / сложно в шаблоне обещание-затем-поймать.

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

3. Создайте настраиваемые ошибки

Создать собственную ошибку очень просто. Вы можете использовать любой настраиваемый класс и выдавать его как ошибку.

class SomeNetworkError {
  constructor(status) {
    this.status = status;
  }
}
try {
  throw new SomeNetworkError(4000);
} catch (error) {
  console.log(error instanceof SomeNetworkError); // true
  console.log(`SomeNetworkError Status: ${error.status}`); // SomeNetworkError Status: 4000
}

Выше у нас есть класс SomeNetworkError, и мы используем экземпляр этого класса, чтобы выдать ошибку. Это действующий код. Однако в качестве практики кодирования мы должны расширить стандартные (стандартные) классы ошибок. Базой всех error-классов является Error и вызов супер-методом с сообщением.

class SomeNetworkError extends Error {
  constructor(message, status) {
    super(message);
    this.status = status;
  }
}
try {
  throw new SomeNetworkError("Network Error", 4000);
} catch (error) {
  console.error(error instanceof Error); // true
  console.log(`> ${error}`); // > Error: Network Error
  console.error(error); // SomeNetworkError: Network Error
  console.error(error.stack); // stacktrace here
}

Если вы заметили, расширение класса Error и вызов super автоматически получают метод .toString класса SomeNetworkError. И он печатает красивое сообщение. Точно так же вы можете расширить и другой стандартный класс Error.

class ArithmeticRangeError extends RangeError {
  constructor(message) {
    super(message);
  }
}
try {
  const zero = 0;
  if (zero === 0) {
    throw new ArithmeticRangeError("zero cant be 0");
  }
} catch (error) {
  console.log(error instanceof RangeError); // true
  console.error(error.toString()); // RangeError: zero cant be 0
}

4. Расширенная обработка ошибок

Обработка ошибок с помощью цикла: Не допускайте попадания в цикл try-catch, если вы хотите прервать цикл при ошибке. В противном случае поместите try-catch в цикл, чтобы продолжить

Разрыв при ошибке:

try { 
  const numbers = [10, 2, 0, 5];
  numbers.forEach((num) => {
    if (num === 0) {
      throw new ArithmeticRangeError("zero cant be 0");
    }
    console.log(num);
  });
} catch (error) {} 

Продолжить при ошибке или пропустить:

const numbers = [10, 2, 0, 5];
numbers.forEach((num) => {
  try {
    if (num === 0) {
      throw new ArithmeticRangeError("zero cant be 0");
    }
  } catch (error) {}
  console.log(num);
});

Без try-catch, логический дескриптор:

// filter zero, no need handle zero
const numbers = [10, 2, 0, 5].filter((num) => num !== 0);
numbers.forEach((num) => {
  console.log(num);
});
// Use some to break loop
const numbers = [10, 2, 0, 5];
numbers.some((num) => {
  const isZero = num === 0;
  if (isZero) return true;
  // logic here
  console.log(num);
});

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

Множественные ошибки в try-catch:

try {
  let name ;
  /// some operation
  if (name === "") throw new RangeError("Cant be blank");
  if (name.match(/\W/)) throw new TypeError("name cant be non alph-numric");
  throw new Error("Some other error");
} catch (error) {
  if (error instanceof RangeError) console.log("RangeError");
  else if (error instanceof TypeError) console.log("TypeError");
  else console.log("Other Error");
}

Множественные ошибки в обещании-затем-ловле:

new Promise((resolve) => {
  let name;
  // some logic
  resolve(name);
})
  .then((name) => {
    if (name === "") throw new RangeError("Cant be blank");
    else return name;
  })
  .then((name) => {
    if (name.match(/\W/)) throw new TypeError("name cant be non alph-numric");
    else return name;
  })
  .catch((error) => {
    if (error instanceof RangeError) console.log("RangeError");
    else if (error instanceof TypeError) console.log("TypeError");
    else console.log("Other Error");
  });

5. Правила кодирования при обработке ошибок

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

  1. Создайте константы класса или ошибки
  2. Используйте локализацию с самого начала
  3. Обычный модуль или файл util для обработки логики и генерации ошибки
  4. Постарайтесь свести к минимуму использование try-catch. Вместо этого напишите больше примеров модульного тестирования.
  5. Поймать и выдать настраиваемую ошибку при вызовах API
  6. Используйте машинописный текст, чтобы уменьшить количество ошибок
  7. Сведите к минимуму использование магического числа / строки
  8. Избегайте более высокого уровня вложенного объекта
  9. Избегайте глобального загрязнения объекта
  10. Максимально используйте E E rror-Boundaries (React)
  11. Правильное ведение журнала, используйте console.error для регистрации ошибок.
  12. Уровень журнала, чтобы минимизировать количество сообщений журнала.
  13. Не печатать учетные данные в журналах
  14. Используйте больше визуальных элементов, чем консоль в случае веб-приложений.

6. Заключение

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

Надеюсь, вам понравится этот пост. Давайте напишем чистый и безопасный код.