JavaScript
Nuances of Programming

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

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

Исключения лучше, чем возврат кода ошибки

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

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

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

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

const LESS_THAN_ZERO = 'LESS_THAN_ZERO';
const TOO_MANY = 'TOO_MANY';
const NOT_A_NUMBER = 'NOT_A_NUMBER';
class FruitStand {
setNumFruit(numFruits) {
if (typeof numFruits !== 'number') {
return NOT_A_NUMBER;
}
if (numFruits < 0) {
return LESS_THAN_ZERO;
}
if (numFruits > 100) {
return TOO_MANY;
}
this.numFruits = numFruits;
}
}
const fruitStand = new FruitStand();
const error = fruitStand.setNumFruit(1);
if (error !== LESS_THAN_ZERO && error !== TOO_MANY && error !== NOT_A_NUMBER) {
console.log(fruitStand.numFruits);
}

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

Вместо этого используем исключения: 

const LESS_THAN_ZERO = 'LESS_THAN_ZERO';
const TOO_MANY = 'TOO_MANY';
const NOT_A_NUMBER = 'NOT_A_NUMBER';
class FruitStand {
setNumFruit(numFruits) {
if (typeof numFruits !== 'number') {
throw new Error(NOT_A_NUMBER);
}
if (numFruits < 0) {
throw new Error(LESS_THAN_ZERO);
}
if (numFruits > 100) {
throw new Error(TOO_MANY);
}
this.numFruits = numFruits;
}
}
const fruitStand = new FruitStand();
try {
const error = fruitStand.setNumFruit(1);
console.log(fruitStand.numFruits);
} catch (ex) {
console.error(ex);
}

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

Пишите блок Try-Catch-Finally

Стоит оборачивать try в коде, генерирующем исключения, которые мы хотим отловить. Он создаёт собственную область видимости для переменных в области блока, поэтому на объявленное с помощью let или const можно ссылаться только в блоке try.

На переменные, объявленные с var, можно ссылаться вне блока — мы не получим ошибку. Такой код выдаст 1:

try {
var x = 1;
} catch (ex) {
console.error(ex);
}
console.log(x);

А этот код выдаст Uncaught ReferenceError: x is not defined:

try {
let x = 1;
} catch (ex) {
console.error(ex);
}
console.log(x);

Не игнорируйте пойманные ошибки 

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

Сообщения об ошибках позволяют нам узнать об ошибке и корректно обработать её.

Примеры выше, например console.error, вызовут следующее: 

try {
const error = fruitStand.setNumFruit(1);
console.log(fruitStand.numFruits);
} catch (ex) {
console.error(ex);
}

Это один из способов сообщить об ошибке. Также можно использовать другие библиотеки для сообщения об ошибках.

Не игнорируйте отклонённые промисы 

Отклонённые промисы нужно обрабатывать, как и любые другие исключения. Их можно обработать с помощью обратного вызова, который передаётся в метод catch, или с помощью блока try...catch для функций async — они одинаковые. 

Например, об ошибке можно сообщить, написав следующее:

Promise.reject('fail')
.catch(err => {
console.error(err);
})

Или для функций async напишем:

(async () => {
try {
await Promise.reject('fail')
} catch (err) {
console.error(err);
}
})()

Предоставление контекста с помощью исключений

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

Можно получить трассировку стека с исключениями в JavaScript, но она может не предоставлять всю необходимую информацию. 

Заключение

Бросание исключений лучше, чем возврат кода ошибок, так как они позволяют использовать блок try...catch для обработки ошибок. Это намного проще, чем проверка множества кодов ошибок. 

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

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

Наконец, отклонённые ошибки промисов нужно обрабатывать так же, как и остальные исключения. 

Читайте также:

Перевод статьи John Au-Yeung: JavaScript Clean Code: Error Handling