Обработка ошибок: почему приложение ломается молча
Обработка ошибок простыми словами: почему приложение ломается молча, что делать с ошибками на фронтенде и бэкенде и что показывать человеку, а что писать в лог.
Обработка ошибок — это заранее решённый ответ на вопрос «что делать, если не получилось»: запрос не дошёл, база не ответила, человек ввёл не то. Приложение с хорошей обработкой ошибок говорит пользователю, что произошло и что делать дальше, а разработчику — где и почему.
В коде, написанном с ИИ, этот ответ часто отсутствует. Ассистент пишет то, что просили, — счастливый путь, где всё получается. Получается приложение, которое на демо работает, а у живого человека иногда просто ничего не делает. Кнопка нажата, спиннер крутится вечно, в базе пусто, и никто, включая тебя, об этом не знает.
Почему приложение ломается молча
Три частые причины тишины:
- Ошибку поймали и выбросили. Пустой
catch {}илиcatch (e) { console.log(e) }в браузере. Формально ошибка обработана, на деле — спрятана. - Ошибку не ждали вовсе. Запрос к API без проверки ответа: сервер вернул
500, а код читает данные так, будто всё в порядке, и показывает пустой список. - Ошибка случилась там, где никто не смотрит. Фоновая задача, вебхук, функция по расписанию. Упала — и всё, следов нет.
Первое, что стоит сделать в таком проекте, — поиск по коду: catch, .catch(, except. Каждое место, где ошибка молча проглатывается, — кандидат на исправление.
Два адресата: человек и лог
У каждой ошибки два читателя, и им нужно разное.
Человеку нужно понять, что произошло на его стороне и что делать: «Не удалось сохранить, проверь интернет и попробуй ещё раз», «Такой адрес уже зарегистрирован — войди или восстанови пароль». Без технических подробностей, без стека вызовов, без названий таблиц. Внутренности системы в сообщении об ошибке — это ещё и подсказка атакующему.
Логу нужно всё, чтобы разобраться потом: что за операция, какой пользователь (идентификатор, а не почта), какие входные параметры, полный текст ошибки и стек. Но без паролей, токенов и номеров карт.
Хорошая практика — одно и то же событие отправить в оба места: короткое понятное сообщение человеку и подробную запись в лог, связанные общим идентификатором. Тогда на письмо в поддержку «у меня ошибка, код A7F3» можно найти конкретную запись.
Обработка ошибок на фронтенде
На стороне браузера задача — не оставлять человека в неизвестности.
- У каждого запроса три состояния: загрузка, успех и ошибка. Если в компоненте есть только первые два, третье случится всё равно — просто без интерфейса.
- Проверяй ответ сервера.
fetchне считает ответ404или500ошибкой: нужно смотреть наresponse.okсамостоятельно. - Не теряй введённое. Если отправка формы не удалась, данные должны остаться в полях. Заставить человека заново заполнять длинную форму — надёжный способ его потерять.
- Ставь общую страховку. В React это граница ошибок — компонент, который вместо белого экрана показывает «что-то пошло не так» с кнопкой «обновить».
- Отправляй ошибки браузера к себе. Без сервиса сбора ошибок ты не узнаешь, что у пользователей с определённым браузером всё падает.
Обработка ошибок на бэкенде
На сервере задача — не врать о результате и не терять информацию.
- Правильные коды ответа. Ошибка ввода —
400, нет доступа —403, не найдено —404, сломалось у нас —500. Ответ200с текстом «ошибка» внутри ломает всю логику на клиенте. - Проверка на входе. Чем раньше отбракованы плохие данные, тем меньше странных ошибок глубже.
- Внешние сервисы падают. Ставь таймауты на запросы к ним и решай заранее, что будет при отказе: повторить, поставить в очередь или честно вернуть ошибку.
- Не делай половину операции. Если надо списать баланс и создать заказ, это одна транзакция: либо оба шага, либо ни одного.
Минимальный шаблон для обработчика выглядит примерно так:
try {
const order = await createOrder(input);
return res.status(201).json(order);
} catch (err) {
const id = crypto.randomUUID();
logger.error({ id, userId: user.id, err }, "createOrder failed");
return res.status(500).json({ error: "Не удалось оформить заказ", id });
}
Что не надо делать
- Показывать человеку текст исключения как есть.
- Ловить все ошибки подряд и продолжать, как будто ничего не случилось.
- Бесконечно повторять запрос, который падает каждый раз, — особенно к платному API.
- Писать в лог всё тело запроса целиком, вместе с паролем.
Коротко
- Обработка ошибок — заранее решённый ответ на «что, если не получилось».
- Самая опасная ошибка — проглоченная: пустой
catchхуже упавшего приложения. - Человеку — понятное сообщение и следующий шаг, логу — подробности без секретов, между ними — общий идентификатор.
- На фронтенде у каждого запроса есть состояние ошибки, а введённые данные не теряются.
- На бэкенде — честные коды ответа, таймауты на внешние сервисы и транзакции.
Если в проекте десятки пустых catch, SUBXCAT может разобрать их и настроить сбор ошибок, чтобы молчаливые поломки стали видимыми.