Далі йде узагальнення та курація з багатьох різних джерел на цю тему, включаючи приклад коду та цитати з вибраних публікацій блогу. Повний перелік найкращих практик можна знайти тут
Найкращі практики обробки помилок Node.JS
Номер1: Використовуйте обіцянки для обробки помилок асинхронізації
TL; DR: Поводження з асинхронними помилками у стилі зворотного дзвінка - це, мабуть, найшвидший шлях до пекла (він же піраміда приреченості). Найкращим подарунком, який ви можете подарувати своєму коду, є використання замість цього поважної бібліотеки обіцянок, яка забезпечує набагато компактний і звичний синтаксис коду, як-от пробування
Інакше: стиль зворотного виклику Node.JS (функція (помилка, відповідь)) - це багатообіцяючий спосіб нерентабельного коду за рахунок поєднання помилок з випадковим кодом, надмірного введення та незручного шаблону кодування.
Приклад коду - хороший
doWork()
.then(doWork)
.then(doError)
.then(doWork)
.catch(errorHandler)
.then(verify);
приклад коду анти шаблон - обробка помилок стилю зворотного виклику
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){
...
});
});
});
});
});
Цитата в блозі: "У нас проблема з обіцянками"
(З блогу pouchdb, що займає 11 місце за ключовими словами "Вузол обіцяє")
"... Насправді, зворотні виклики роблять щось ще більш зловісне: вони позбавляють нас стека, що зазвичай ми сприймаємо як належне в мовах програмування. Написання коду без стека - це дуже схоже на керування автомобілем без педалі гальма: ви не розумійте, як сильно вам це потрібно, поки ви не досягнете цього, і його там немає. Вся суть обіцянок полягає в тому, щоб повернути нам ті основи мови, які ми втратили, коли ми перейшли в асинхровку: повернення, кидання та стек. Але ти повинні знати, як правильно використовувати обіцянки, щоб скористатися ними ".
Число2: Використовуйте лише вбудований об'єкт Помилка
TL; DR: Досить часто бачити код, який видаляє помилки як рядок або як власний тип - це ускладнює логіку обробки помилок та сумісність між модулями. Якщо ви відхиляєте обіцянку, викидаєте виняток або випускаєте помилку - використання вбудованого об'єкта Error Node.JS збільшує рівномірність та запобігає втраті інформації про помилку
В іншому випадку: виконуючи якийсь модуль, будучи невідомим, який тип помилок приходить взамін - це значно важче міркувати про майбутній виняток і обробляти його. Навіть варто застосувати користувальницькі типи для опису помилок, що може призвести до втрати критичної інформації про помилку, наприклад, сліду стека!
Приклад коду - робити це правильно
//throwing an Error from typical function, whether sync or async
if(!productToAdd)
throw new Error("How can I add new product when no value provided?");
//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));
//'throwing' an Error from a Promise
return new promise(function (resolve, reject) {
DAL.getProduct(productToAdd.id).then((existingProduct) =>{
if(existingProduct != null)
return reject(new Error("Why fooling us and trying to add an existing product?"));
приклад коду анти-шаблон
//throwing a String lacks any stack trace information and other important properties
if(!productToAdd)
throw ("How can I add new product when no value provided?");
Цитата в щоденнику: "Рядок не є помилкою"
(З блогу devthought, 6 місце для ключових слів "Node.JS error error")
"... передача рядка замість помилки призводить до зменшення сумісності між модулями. Це розриває контракти з API, які можуть виконувати instanceof перевірку помилок, або які хочуть дізнатися більше про помилку . Об'єкти помилок, як ми побачимо, мають дуже цікаві властивості в сучасних JavaScript-системах, окрім того, що повідомлення передане конструктору .. "
Номер 3: Розрізняють оперативні та програмові помилки
TL; DR: Помилки операцій (наприклад, API отримано недійсний ввід) відносяться до відомих випадків, коли вплив помилки повністю зрозумілий і з ним можна обдумувати продумано. З іншого боку, помилка програміста (наприклад, спроба прочитати невизначену змінну) посилається на невідомі збої коду, які диктують витончений перезапуск програми
В іншому випадку: Ви завжди можете перезапустити програму, коли з’явиться помилка, але навіщо опускати ~ 5000 онлайн-користувачів через незначну та передбачувану помилку (операційна помилка)? навпаки, також не ідеально - тримати додаток, коли невідома проблема (помилка програміста) може призвести до непередбачуваної поведінки. Диференціація обох дозволяє діяти тактовно та застосовувати збалансований підхід на основі заданого контексту
Приклад коду - робити це правильно
//throwing an Error from typical function, whether sync or async
if(!productToAdd)
throw new Error("How can I add new product when no value provided?");
//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));
//'throwing' an Error from a Promise
return new promise(function (resolve, reject) {
DAL.getProduct(productToAdd.id).then((existingProduct) =>{
if(existingProduct != null)
return reject(new Error("Why fooling us and trying to add an existing product?"));
приклад коду - позначення помилки як оперативної (довіреної)
//marking an error object as operational
var myError = new Error("How can I add new product when no value provided?");
myError.isOperational = true;
//or if you're using some centralized error factory (see other examples at the bullet "Use only the built-in Error object")
function appError(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);
//error handling code within middleware
process.on('uncaughtException', function(error) {
if(!error.isOperational)
process.exit(1);
});
Цитата в блозі : "Інакше ви ризикуєте станом" (З блогу, який виводиться з ладу, 3 місце за ключовими словами "Виключення Node.JS uncaught")
" ... За своєю суттю, як працює кидок у JavaScript, майже ніколи немає способу безпечно" забрати, де ви зупинилися ", не протікаючи посилань чи створюючи якийсь інший не визначений крихкий стан. Найбезпечніший спосіб відповісти на викинута помилка полягає в тому, щоб вимкнути процес . Звичайно, на звичайному веб-сервері у вас може бути відкрито багато з’єднань, і нерозумно їх різко закривати, оскільки помилку викликав хтось інший. Кращий підхід - це надіслати відповідь про помилку на запит, який викликав помилку, а інші дозволяють закінчити звичайний час і перестати слухати нові запити у цього працівника "
Number4: Обробляйте помилки централізовано, через, але не в межах програмного забезпечення
TL; DR: Логіка керування помилками, такими як пошта для адміністратора та ведення журналів, повинна бути інкапсульована у спеціалізований та централізований об'єкт, який усі кінцеві точки (наприклад, середнє програмне забезпечення Express, завдання Cron, тестування блоків) викликають, коли виникає помилка.
Інакше: Якщо не обробляти помилки в одному місці, це призведе до дублювання коду і, ймовірно, до помилок, з якими неправильно обробляється
Приклад коду - типовий потік помилок
//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(function (result) {
res.status(200).json(result);
}).catch((error) => {
next(error)
});
}
catch (error) {
next(error);
}
//Error handling middleware, we delegate the handling to the centrzlied error handler
app.use(function (err, req, res, next) {
errorHandler.handleError(err).then((isOperationalError) => {
if (!isOperationalError)
next(err);
});
});
Цитата в щоденнику: "Іноді більш низькі рівні не можуть зробити нічого корисного, окрім розповсюдження помилки своєму абоненту" (З блогу Joyent, що займає 1 місце за ключовими словами "Помилка Node.JS")
"... Ви можете отримати одну і ту ж помилку на декількох рівнях стеку. Це трапляється, коли нижчі рівні не можуть зробити нічого корисного, крім того, щоб поширити помилку своєму абоненту, який поширює помилку на її абонента тощо". тільки той, хто телефонує верхнього рівня, знає, що відповідає відповідній реакції, чи потрібно це повторити операцію, повідомити про помилку користувачеві чи щось інше. Але це не означає, що ви повинні намагатися повідомляти про всі помилки на одному верхньому рівні зворотний виклик, оскільки сам зворотний виклик не може знати, в якому контексті сталася помилка "
Number5: Помилки API документа за допомогою Swagger
TL; DR: Повідомте ваших абонентів API знати, які помилки можуть прийти взамін, щоб вони могли обдумати ці помисли, без збоїв. Зазвичай це робиться з документаційними рамками REST API, такими як Swagger
В іншому випадку: клієнт API може вирішити збій і перезапустити лише тому, що отримав назад помилку, яку він не міг зрозуміти. Примітка: абонент API може бути ви (дуже типово в середовищі мікросервісів)
Цитата в блозі: "Ви повинні сказати своїм абонентам, які помилки можуть трапитися" (З блогу Joyent, 1 місце для ключових слів "Node.JS logging")
… Ми говорили про те, як поводитися з помилками, але коли ви пишете нову функцію, як ви доставляєте помилки в код, який називав вашу функцію? … Якщо ви не знаєте, які помилки можуть статися або не знаєте, що вони означають, то ваша програма не може бути виправлена, крім випадкового випадку. Отже, якщо ви пишете нову функцію, ви повинні сказати своїм абонентам, які помилки можуть трапитися і що вони викликають
Номер 6: Вимкніть процес витончено, коли в місто приїжджає незнайомець
TL; DR: Коли трапляється невідома помилка (помилка розробника, див. Кращу практику №3) - існує непевність щодо здоров'я додатків. Поширена практика пропонує ретельно запустити процес, використовуючи інструмент «перезапуск», як Forever та PM2
В іншому випадку: Якщо трапляється незнайомий виняток, якийсь об'єкт може опинитися у несправному стані (наприклад, випромінювач подій, який використовується у всьому світі і більше не запускає події через деякий внутрішній збій), і всі майбутні запити можуть вийти з ладу або поводити себе шалено
Приклад коду - вирішення питання щодо збою
//deciding whether to crash when an uncaught exception arrives
//Assuming developers mark known operational errors with error.isOperational=true, read best practice #3
process.on('uncaughtException', function(error) {
errorManagement.handler.handleError(error);
if(!errorManagement.handler.isTrustedError(error))
process.exit(1)
});
//centralized error handler encapsulates error-handling related logic
function errorHandler(){
this.handleError = function (error) {
return logger.logError(err).then(sendMailToAdminIfCritical).then(saveInOpsQueueIfCritical).then(determineIfOperationalError);
}
this.isTrustedError = function(error)
{
return error.isOperational;
}
Цитата в блозі: "Є три школи думок щодо поводження з помилками" (З блогу jsrecipes)
… Існує, головним чином, три школи думок щодо поводження з помилками: 1. Дозвольте програмі вийти з ладу та перезавантажте її. 2. Обробляйте всі можливі помилки і ніколи не збивайтесь. 3. Збалансований підхід між ними
Число7: Використовуйте зрілий реєстратор для збільшення видимості помилок
TL; DR: Набір зрілих засобів ведення журналів, таких як Вінстон, Бунян або Log4J, прискорить виявлення та розуміння помилок. Тож забудьте про console.log.
В іншому випадку: Снімінг через console.logs або вручну через безладний текстовий файл без інструментів для запитів або пристойний переглядач журналу може зайняти вас на роботі до пізнього часу
Приклад коду - реєстратор Winston в дії
//your centralized logger object
var logger = new winston.Logger({
level: 'info',
transports: [
new (winston.transports.Console)(),
new (winston.transports.File)({ filename: 'somefile.log' })
]
});
//custom code somewhere using the logger
logger.log('info', 'Test Log Message with some parameter %s', 'some parameter', { anything: 'This is metadata' });
Цитата в блозі: "Дозволяє визначити кілька вимог (для лісоруба):" (З блогу strongblog)
… Давайте визначимо декілька вимог (для реєстратора): 1. Маркуйте час кожного рядка журналу. Цей досить пояснюючий сам - ви повинні мати можливість сказати, коли відбувся кожен запис журналу. 2. Формат журналу повинен бути легко засвоюваним як людиною, так і машинами. 3. Дозволяє для декількох настроюваних потоків призначення. Наприклад, ви можете писати журнали слідів до одного файлу, але коли виникає помилка, запишіть у той самий файл, потім у файл помилок і одночасно надішліть електронний лист ...
Номер8: Дізнайтеся про помилки та простої за допомогою продуктів APM
TL; DR: Моніторингові та продуктивні продукти (ака APM) активно вимірюють вашу кодову базу чи API, щоб вони могли автоматично магічно виділяти помилки, збої та повільні частини, які вам бракували
В іншому випадку: Ви можете витратити великі зусилля на вимірювання продуктивності та простоїв API, ймовірно, ви ніколи не дізнаєтесь, які є ваші найповільніші частини коду за сценарієм реального світу та як це впливає на UX
Цитата в блозі: "Сегменти продуктів APM" (З блогу Yoni Goldberg)
"... Продукти APM складають 3 основні сегменти: 1. Моніторинг веб-сайтів або API - зовнішні сервіси, які постійно контролюють тривалість роботи та продуктивність за допомогою HTTP-запитів. Налаштування може бути налаштовано за кілька хвилин. Наступні декілька обраних претендентів: Pingdom, Uptime Robot і New Relic
2 Кодування приладів - сімейство продуктів, яким потрібно вбудовувати агент у додаток, щоб використовувати функцію повільного виявлення коду, статистику винятків, моніторинг продуктивності та багато іншого. Нижче перелічено декілька вибраних претендентів: New Relic, App Dynamics
3. Інформаційна панель операційної розвідки -ця лінійка продуктів орієнтована на полегшення команді ops за допомогою метрик та кураторного вмісту, що допомагає легко бути в курсі продуктивності програми. Зазвичай це включає агрегування декількох джерел інформації (журнали додатків, журнали БД, журнал серверів тощо) та роботи по проектуванню панелі наперед. Нижче перелічено декількох обраних претендентів: Datadog, Splunk "
Наведене вище є скороченою версією - див. Тут більше кращих практик та прикладів