Список лучших практик для обработки ошибок в приложении, он может быть применен к любому фреймворку.
Обратные вызовы плохо масштабируются, поскольку большинство программистов с ними не знакомо. Они заставляют проверять ошибки повсюду, разбираются с неприятным вложением кода и затрудняют рассуждение о потоке кода. Библиотеки Promise, такие как BlueBird, async и Q, содержат стандартный стиль кода с использованием RETURN и THROW для управления потоком программы. В частности, они поддерживают любимый стиль обработки ошибок try-catch, который позволяет освободить основной путь кода от ошибок в каждой функции.
Пример кода - использование обещаний для обнаружения ошибок
return functionA() .then((valueA) => functionB(valueA)) .then((valueB) => functionC(valueB)) .then((valueC) => functionD(valueC)) .catch((err) => logger.error(err)) .then(alwaysExecuteThisFunction())
Пример кода - использование async / await для обнаружения ошибок
async function executeAsyncTask () { try { const valueA = await functionA(); const valueB = await functionB(valueA); const valueC = await functionC(valueB); return await functionD(valueC); } catch(err) { logger.error(err); } }
Пример антишаблонного кода
getData(someParameter, function(err, result) { if(err !== null) { // do something like calling the given callback function and pass the error getMoreData(a, function(err, result) { if(err !== null) { // do something like calling the given callback function and pass the error getMoreData(b, function(c) { getMoreData(d, function(e) { if(err !== null ) { // you get the idea? } }) }); } }); } });
Как отличить операционные ошибки от ошибок программиста
Выделение следующих двух типов ошибок минимизирует время простоя вашего приложения и помогает избежать сумасшедших ошибок. Под эксплуатационными ошибками понимаются ситуации, когда вы понимаете, что произошло и как это повлияло - например, запрос к какой-либо службе HTTP завершился неудачно из-за проблемы с подключением. С другой стороны, ошибки программиста относятся к случаям, когда вы не знаете, почему и иногда откуда возникла ошибка - это может быть какой-то код, который пытался прочитать неопределенное значение или пул соединений с БД, который приводит к утечке памяти. С операционными ошибками относительно легко справиться - обычно достаточно регистрации ошибки. Все становится непросто, когда появляется ошибка программиста, приложение может находиться в несогласованном состоянии, и нет ничего лучше, чем вы можете сделать лучше, чем изящно перезапустить
Пример кода
class AppError { constructor (commonType, description, isOperational) { Error.call(this); Error.captureStackTrace(this); this.commonType = commonType; this.description = description; this.isOperational = isOperational; } }; throw new AppError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true);
Централизованно обрабатывайте ошибки. Не в промежуточном программном обеспечении
Без одного выделенного объекта для обработки ошибок выше вероятность того, что важные ошибки будут скрыты под радаром из-за неправильной обработки. Объект обработчика ошибок отвечает за то, чтобы сделать ошибку видимой, например, путем записи в хорошо отформатированный регистратор, отправки событий в некоторый продукт мониторинга, такой как Sentry, Rollbar или Raygun. Большинство веб-фреймворков, таких как Express, предоставляют механизм промежуточного программного обеспечения для обработки ошибок. Типичный поток обработки ошибок может быть следующим: какой-то модуль выдает ошибку - ›маршрутизатор API улавливает ошибку -› он передает ошибку промежуточному программному обеспечению (например, Express, KOA), которое отвечает за обнаружение ошибок - ›вызывается централизованный обработчик ошибок - ›Промежуточному программному обеспечению сообщается, является ли эта ошибка ненадежной ошибкой (нерабочей), чтобы оно могло корректно перезапустить приложение. Обратите внимание, что это обычная, но неправильная практика обработки ошибок в промежуточном программном обеспечении Express - это не касается ошибок, возникающих в не веб-интерфейсах.
Пример кода - типичный поток ошибок
// DAL layer, we don't handle errors here DB.addDocument(newCustomer, (error, result) => { if (error) throw new Error("Great error explanation comes here", other useful parameters) }); // API route code, we catch both sync and async errors and forward to the middleware try { customerService.addNew(req.body).then((result) => { res.status(200).json(result); }).catch((error) => { next(error) }); } catch (error) { next(error); } // Error handling middleware, we delegate the handling to the centralized error handler app.use(async (err, req, res, next) => { const isOperationalError = await errorHandler.handleError(err); if (!isOperationalError) { next(err); } });
Пример кода - обработка ошибок в выделенном объекте
module.exports.handler = new errorHandler(); function errorHandler() { this.handleError = async function(err) { await logger.logError(err); await sendMailToAdminIfCritical; await saveInOpsQueueIfCritical; await determineIfOperationalError; }; }
Пример кода - Anti Pattern: обработка ошибок в промежуточном программном обеспечении
// middleware handling the error directly, who will handle Cron jobs and testing errors? app.use((err, req, res, next) => { logger.logError(err); if (err.severity == errors.high) { mailer.sendMail(configuration.adminMail, 'Critical error occured', err); } if (!err.isOperational) { next(err); } });