Представьте: вы пытаетесь забронировать билет на концерт в 2 часа ночи, лишены кофеина, но полны решимости. Сайт выдаёт ошибку — «SYSTEM_ERR_CODE 0xDEADBEEF: Неверное выравнивание конденсатора потока». Внезапно вы боретесь не только с недосыпом, но и с экзистенциальным страхом. Вот почему обработка ошибок важнее, чем последний синтаксический сахар вашего любимого фреймворка.
Давайте превратим этих цифровых провокаторов гнева во что-то полезное для пользователей (и спасём ваш почтовый ящик службы поддержки). Вот мой проверенный в боях рецепт сообщений об ошибках, которые не раздражают.
1. Создайте свою таксономию ошибок как мастер покемонов
Подобно тому как Пикачу эволюционирует в Райчу, вашим ошибкам нужна правильная родословная. Давайте создадим иерархию ошибок, которой гордился бы Дарвин:
class ApplicationError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
// Ошибки валидации — семейство «вы ошиблись»
class ValidationError extends ApplicationError {}
class RequiredFieldError extends ValidationError {
constructor(field) {
super(`Подождите! Нам нужно ваше ${field} для продолжения`);
this.field = field;
}
}
// Ошибки API — когда ударяют интернетные гремлины
class APIError extends ApplicationError {
constructor(endpoint) {
super(`Нашим хомячкам, работающим с ${endpoint}, нужен перерыв на кофе`);
this.retryAfter = 30000;
}
}
Теперь вы можете ловить конкретные ошибки как профессионал:
try {
await submitForm();
} catch (error) {
if (error instanceof RequiredFieldError) {
highlightMissingField(error.field);
} else if (error instanceof APIError) {
showRetryButton(error.retryAfter);
} else {
// Наша финальная форма: неожиданные ошибки
logToSentry(error);
showPanicMessage();
}
}
Ваша таксономия ошибок должна расти как хорошо организованный набор инструментов, а не как ящик с кучей случайных USB-кабелей. Группируйте ошибки по доменам (валидация, API, аутентификация) и наследуйте их ответственно.
2. Пишите сообщения как человек, а не HAL 9000
Плохие сообщения об ошибках похожи на неудачные первые свидания — сбивают с толку, расстраивают и заставляют гадать, что пошло не так. Давайте исправим это с помощью принципа WAIT:
- What happened? (Что случилось?)
- Action required (Требуется действие)
- Instructions (необязательно) (Инструкции)
- Tech details (скрыты) (Технические подробности) Хорошее, плохое и «WTF»:
// Плохо: «Неверный ввод»
// Хуже: «ERR_CODE 418: Переполнение чайника»
// Лучше: «Ой! В адресе электронной почты нужен символ @. Например, [email protected]»
class InvalidEmailError extends ValidationError {
constructor(value) {
super(`В адресе электронной почты нужен символ @. Мы получили: ${value}`);
this.example = "[email protected]";
}
}
// Бонус: добавьте устранение неполадок в объект ошибки
error.helpDocs = "https://example.com/email-format-guide";
Совет: храните понятные пользователю сообщения отдельно от кодов ошибок. Представьте, что это инструкции IKEA — простые картинки для пользователей, номера деталей для поддержки.
3. Цирк обработки ошибок: Try/Catch/Finally Edition
Обработка ошибок похожа на трёхактное цирковое представление. Давайте разберём основные номера:
Пример из реальной жизни с очисткой ресурсов:
async function processOrder(userId) {
let dbConnection;
try {
dbConnection = await connectToDatabase();
const cart = await dbConnection.getCart(userId);
if (!cart.items.length) {
throw new ValidationError("Ваша корзина пуста, как холодильник студента");
}
const paymentResult = await processPayment(cart);
return await createOrder(dbConnection, paymentResult);
} catch (error) {
if (error instanceof ValidationError) {
await sendUserNotification(userId, error.message);
} else {
await logErrorToService(error);
await triggerIncidentWorkflow(error);
}
throw error; // Пусть обработчики выше решат
} finally {
if (dbConnection) {
await dbConnection.cleanup();
}
}
}
Обратите внимание, как мы:
- Инициализируем ресурсы вне блока try.
- Обрабатываем ожидаемые ошибки (валидацию).
- Выполняем очистку в блоке finally — важно для предотвращения утечек памяти.
- Повторно генерируем исключение для глобального обработчика.
4. Вскрытие ошибки: ведение журнала как команда CSI
Когда возникают ошибки, вам нужно больше улик, чем в романе Агаты Кристи. Реализуйте 5 Ws ведения журнала ошибок:
class ErrorLogger {
static log(error) {
const entry = {
когда: new Date().toISOString(),
что: error.message,
где: error.stack?.split('\n')?.trim(),
кто: getCurrentUser()?.id || 'аноним',
почему: error.constructor.name,
как: {
код: error.code,
дополнительно: error.context
}
};
sendToLogService(entry);
if (!error.isOperational) {
triggerPagerDuty(entry);
}
}
}
// Использование
try {
riskyOperation();
} catch (error) {
ErrorLogger.log(error);
throw error;
}
Ваши журналы должны рассказывать историю яснее, чем документальный фильм Netflix о реальных преступлениях. Включайте:
- Контекст пользователя (без персональных данных).
- Трассировку стека.
- Код ошибки.
- Сведения о среде.
- Хлебные крошки (предыдущие действия).
5. Набор выживания пользователя: версия для фронтенда
В мире React аварийные границы — это ваши аварийные сигналы. Давайте создадим такой, который будет полезнее GPS с разряженными батареями:
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
logErrorToService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>⚠️ Ну ничего себе!</h2>
<p>Этот компонент вышел из строя. Мы послали ниндзя кодирования, чтобы исправить это.</p>
<button onClick={this.handleRetry}>Повторить</button>
<details>
<summary>Технические подробности для ботаников</summary>
<code>{this.state.error?.toString()}</code>
</details>
</div>
);
}
return this.props.children;
}
}
Ключевые особенности:
- Дружелюбное сообщение (без технических терминов).
- Возможность восстановления.
- Скрытые технические подробности.
- Автоматическая отчётность об ошибках.
6. Искусство разработки, управляемой ошибками
Превратите ошибки в функции с этим циклом разработки:
- Отслеживайте распространённые ошибки в продакшне.
- Анализируйте закономерности (неверные входные данные? Неустойчивость API?).
- Предотвращайте с помощью:
- Лучшей валидации.
- Логики повторных попыток.
- Обучения пользователей.
- Следите за улучшениями. Пример: если вы видите частые ошибки «Invalid Date» (недопустимая дата):
// До
<input type="text" name="birthdate">
// После
<DatePicker