13 февр. 2025 г.·7 мин чтения

Архитектура обработки ошибок для фронтенд‑команд, которая масштабируется

Постройте архитектуру обработки ошибок, которую команда фронтенда сможет переиспользовать на всех экранах: с понятными повторами, сообщениями для поддержки и простыми правилами проверки.

Архитектура обработки ошибок для фронтенд‑команд, которая масштабируется

Почему у команд появляются запутанные состояния ошибок

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

Пользователи замечают эту непоследовательность сразу. Если на одном экране написано "Попробуйте снова", а на другом только "Произошла ошибка", люди перестают доверять сообщению. Они не знают, сохранилось ли действие, безопасно ли повторять попытку или стоит ли писать в поддержку.

Обычно это начинается по простым причинам. Разные команды выпускают экраны в разное время. Ошибки бэкенда приходят в разных форматах. Дизайнеры сначала прорабатывают успешные сценарии и оставляют состояния ошибок на потом. Разработчики под давлением сроков делают заплатки. Никто не отвечает за общий паттерн по всему продукту.

В результате всё кажется случайным. Клиент меняет пароль и видит понятное сообщение при обрыве сети. Позже тот же клиент пытается изменить адрес для выставления счёта и получает пустую форму без подсказки, что пошло не так. Даже если оба сбоя были из‑за одного и того же таймаута API, продукт уже воспринимается непоследовательно.

Службы поддержки расплачиваются за эту непоследовательность. Когда сообщения расплывчаты или отсутствуют, в тикетах почти нет полезной информации: "Сломалось", "Страница зависла" или "Я нажал сохранить и ничего не произошло". Поддержка задаёт дополнительные вопросы, инженеры ищут по логам, и все тратят по 20 минут на проблему, которую лучшее сообщение могло сузить за секунды.

Решение — не в большем количестве кастомного UI. Командам нужен один паттерн обработки ошибок, которому сможет следовать любой экран. Этот паттерн должен каждый раз отвечать на одни и те же вопросы: что пошло не так, можно ли повторить попытку, что делать дальше и какие детали нужны поддержке.

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

Выберите ошибки, от которых человек может восстановиться

Спокойный интерфейс начинается с простого разделения: пользовательские ошибки с одной стороны и сбои системы с другой. Если кто‑то ввёл неправильный пароль, указал недопустимую дату или оставил обязательное поле пустым, это можно исправить прямо сейчас. Если сервер таймаутит или фоновая задача упала, обычно нельзя.

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

Большинству команд нужно всего четыре действия:

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

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

Будьте строги в том, что относите к группе восстанавливаемых ошибок. Если причина вне контроля пользователя, не стройте видимость контроля. "Пожалуйста, попробуйте снова" нормально для таймаута. Это плохое сообщение для ошибки прав доступа, повреждённого ответа или серверного правила, которое UI не может объяснить.

Большинству продуктов достаточно короткого каталога восстанавливаемых случаев. 10–15 распространённых ошибок часто хватает для первого прохода. Если список растёт слишком быстро, команда обычно начинает именовать детали бэкенда вместо пользовательских ситуаций.

Сделайте эти выборы рано, и все будут работать по одной карте. Дизайнеры, фронтенд‑разработчики и служба поддержки перестанут гадать, а экраны — придумывать собственные правила восстановления.

Создайте одну общую форму ошибки

Когда каждый экран возвращает разный объект ошибки, фронтенд начинает догадываться. Один компонент показывает тост, другой открывает модал, третий просто выводит сырой текст на страницу. Общая форма быстро это исправит и даст команде одно правило для следования.

Держите форму маленькой. Большинству команд хватает пяти полей:

  • code: короткий стабильный идентификатор, например AUTH_EXPIRED или PAYMENT_TIMEOUT
  • userMessage: понятный текст для экрана
  • supportDetail: техническая заметка для логов, поддержки или для копирования
  • retry: простые правила вроде разрешён/запрещён, время ожидания и макс. попыток
  • context: безопасные факты, например HTTP‑статус, имя сервиса или request id

Это работает, потому что у каждого поля есть одна задача. Пользовательское сообщение говорит человеку, что случилось и что делать дальше. Деталь для поддержки помогает коллеге проследить проблему, не угадывая, какой API упал.

Стабильные коды стоят небольшого усилия. Они позволяют сопоставлять множество бэкенд‑сбоев с одним понятным UI‑паттерном и дают поддержке одинаковую метку каждый раз. Не строьте коды из сырого текста бэкенда. Придумайте их один раз, держите стабильными и используйте в логах, аналитике и заметках к тикетам.

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

Простая форма может выглядеть так:

{
  "code": "PAYMENT_TIMEOUT",
  "userMessage": "We could not confirm your payment. Try again in a minute.",
  "supportDetail": "billing confirm call timed out, request_id=9f2c, status=504",
  "retry": { "allowed": true, "afterMs": 60000, "maxAttempts": 2 },
  "context": { "service": "billing", "status": 504 }
}

Держите сырой текст бэкенда вне UI. Если сервер присылает SQLSTATE‑ошибки, трассировки или сообщения вендора, логируйте их и переводите прежде, чем они попадут на экран. Людям нужен понятный следующий шаг, а не отчёт о крахе бэкенда.

Пишите сообщения, по которым люди могут действовать

Хорошее сообщение об ошибке помогает человеку завершить задачу. Плохое просто сообщает, что приложение недовольно. Когда люди видят "Что‑то пошло не так" или "Запрос не удался", они останавливаются, догадываются или пишут в поддержку со скриншотом и без контекста.

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

Пара примеров показывает разницу:

  • "Запрос не удался" оставляет пользователя в тупике.
  • "Не удалось загрузить файл. Проверьте подключение и попробуйте снова." даёт понятный следующий шаг.
  • "Ошибка валидации" скрывает реальную проблему.
  • "Введите адрес электронной почты в формате [email protected]." говорит человеку, как исправить.

Простой язык ещё важнее, когда пользователь ничего не сделал неправильно. Избегайте обвинительного тона. "Вы ввели неверные данные" звучит холодно и часто не указывает на суть. "Не удалось сохранить форму, потому что не указанa дата" яснее и легче принять.

Технические детали держите вне основного сообщения. Большинству пользователей не нужны HTTP‑коды, трассировки, термины БД или имена вендоров. Поместите эти детали в логи, мониторинг или заметки для поддержки. Если поддержке потребуется трассировка, покажите короткий референс‑код, например "Код ошибки: INV‑204", а остальное скрывайте.

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

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

Планируйте потоки повторных попыток шаг за шагом

Проверить фронтенд‑паттерны
Просмотрите экраны, которые создают наибольшее недоразумение и тикеты в поддержку.

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

Начните с картирования каждого действия на странице. Задайте простой вопрос: если этот запрос выполнится снова, что может пойти не так? Запросы только для чтения обычно можно повторять безопасно. Действия, которые создают, обновляют, удаляют или отправляют что‑то, требуют больше осторожности, даже если кнопка выглядит безобидно.

Набор простых правил подходит для большинства продуктов:

  • Автоматически повторять выборки данных, фоновые обновления и другие read‑запросы.
  • Повторять обновления только если бэкенд использует request id или проверку версий, чтобы блокировать дубликаты.
  • Держать платежи, разрушительные действия и одноразовые отправки на ручном повторе.
  • Если ответ неясен, сначала выбирайте ручной повтор.

Обратная связь во время повтора так же важна, как и само правило. Когда приложение начинает новую попытку, покажите состояние загрузки с объяснением, что происходит. Отключайте кнопку действия, пока запрос выполняется, чтобы люди не нажимали снова и не создавали беспорядок. Если приложение будет пытаться снова через задержку, скажите об этом простыми словами, например: "Попробуем снова через 5 секунд."

Короткая пауза между попытками обычно воспринимается лучше, чем мгновенные повторения. Две‑три попытки хватает для большинства экранов. После этого остановитесь. Бесконечные циклы тратят батарею, скрывают реальную причину и усложняют поддержку, потому что никто не знает, когда приложение фактически сдалось.

Простой ритм для большинства экранов

  1. При первой неудаче выполнить одну повторную попытку в фоне, если запрос безопасен.
  2. Если снова не удалось, сохранить экран в стабильном состоянии и сказать пользователю, когда начнётся следующая попытка.
  3. После финальной неудачной попытки прекратить повторы и показать один понятный следующий шаг.

Представьте панель, которая загружает данные аккаунта, когда поезд заходит в туннель. Приложение может тихо повторить выборку один‑два раза. Денежный перевод — другое дело. Если первая попытка не удалась, приложение должно остановиться, ясно показать статус и ждать решения пользователя. Это небольшое отличие предотвращает дублирование действий и сокращает массу лишних тикетов в поддержку.

Простой пример на одном экране

Хорошим тест‑кейсом будет форма консультации на сайте советников. Основатель вводит имя, email, стадию компании и короткое описание проблемы. Он нажимает "Отправить", спиннер крутится несколько секунд, а затем запрос таймаутит.

Первый ответ должен оставаться спокойным и конкретным. Не очищайте форму. Покажите короткое сообщение рядом с кнопкой: "Мы не смогли отправить запрос — соединение заняло слишком много времени. Ваши данные всё ещё здесь." Это скажет человеку, что произошло, и снимет первый страх: потерю работы.

В надёжном паттерне экран предлагает "Попробовать снова" только когда действие безопасно повторять. Для отправки формы это обычно означает, что приложение отправляет тот же request id, чтобы бэкенд мог игнорировать дубликаты. Если действие не безопасно, экран не должен догадываться.

Простой поток выглядит так:

  1. Запрос на отправку таймаутит.
  2. Форма остаётся заполненной, и кнопка снова становится активной.
  3. Страница показывает одно понятное сообщение об ошибке и кнопку "Попробовать снова".
  4. Если пользователь повторяет попытку и снова не удаётся, страница добавляет сообщение о поддержке.

Сохранение введённых данных важнее, чем команды думают. Если кто‑то написал длинное описание проблемы, заставлять его вводить всё заново часто означает, что он уйдёт. Сохраняйте значения полей, тот же статус экрана и возвращайте фокус к неудачному действию, чтобы он мог быстро попробовать снова.

Сообщение для поддержки стоит показывать после повторной неудачи. После второй неудачной попытки можно показать что‑то вроде: "Всё ещё не работает? Обратитесь в поддержку и укажите код FORM_TIMEOUT." Это коротко, полезно и легко для поиска в логах поддержки.

Такой экранный паттерн кажется маленьким, но он останавливает распространение хаотичного поведения UI по всему продукту. Один таймаут, один стиль сообщения, одно правило повтора и один путь в поддержку уже убирают много путаницы.

Распространённые ошибки, которые порождают больше тикетов в поддержку

Уточнить дизайн ошибок API
Переводите ошибки бэкенда в сообщения, по которым люди могут действовать.

Универсальный тост для всех ошибок — один из самых быстрых способов запутать людей. Если экран всегда пишет "Что‑то пошло не так", пользователи не понимают, повторять ли попытку, ждать, проверять соединение или остановиться, прежде чем сделать что‑то ещё. Сообщение исчезает, контекст уходит вместе с ним, и в поддержку приходит тикет без полезной информации.

Очистка пользовательского ввода после ошибки — так же плохо. Кто‑то заполнил длинную форму, нажал Сохранить, столкнулся с проблемой сети, и экран стёр всё. Большинство людей сначала почувствуют раздражение, а не желание помочь. Сохраняйте данные на экране, отмечайте поле или действие, которое провалилось, и дайте возможность попробовать снова без повторного ввода.

Повторы требуют больше осторожности, чем многие команды ожидают. Повтор беспроблемного fetch обычно безопасен. Повтор действия, который может списать деньги, отправить письмо, создать заказ или пригласить пользователя, может создать дубликаты. Так одна маленькая ошибка превращается в два тикета: один по оригинальной проблеме и один по дублирующей записи.

Сырой текст сервера почти никогда не должен доходить до UI. Ошибки БД, трассировки и внутренние сообщения сервисов не помогают пользователям и часто раскрывают детали, которые лучше держать приватными. Переводите технические сбои на простой язык, а сырые детали оставляйте в логах для команды.

Поддержке трудно работать, когда продукт не даёт ничего полезного. Оставьте короткий след, который смогут использовать и пользователи, и поддержка:

  • Покажите, какое действие не удалось, например "сохранение черновика" или "отправка платежа".
  • Сохраняйте ввод пользователя на экране, когда это безопасно.
  • Включите короткий референс или request id, который пользователь может переслать.
  • Записывайте реальную серверную ошибку в логи, а не в интерфейс.
  • Блокируйте повторные действия, когда вторая попытка может создать дубликаты.

Представьте страницу оформления заказа. Пользователь нажимает "Оплатить", видит расплывчатый тост, нажимает снова и с него снимают деньги дважды. Поддержке теперь приходится гадать, дошёл ли первый запрос до сервера, какому клику принадлежит платеж и что фактически увидел пользователь. Чёткие сообщения, безопасные правила повтора и сохранённый ввод предотвращают эту путаницу ещё до её появления.

Короткий чек‑лист перед релизом

Сократите шум в службе поддержки
Уберите расплывчатые ошибки, чтобы пользователи и поддержка видели следующий шаг быстрее.

Короткая проверка перед релизом ловит большинство ошибок, которые потом превращаются в тикеты. Часто достаточно десяти минут. Цель проста: каждый экран должен обрабатывать сбой одинаково спокойно и предсказуемо.

Если команда не может быстро просмотреть ошибки, система всё ещё слишком рыхлая, и каждый экран уйдёт в свой стиль.

Используйте одни и те же пять проверок каждый раз:

  • Подтвердите, что каждая неудача мапится на один общий паттерн. Таймаут, проблема с правами, пустой результат и проблема сервера не должны давать четыре разных кастомных состояния, если экран действительно этого не требует.
  • Прочитайте сообщение рядом с неудавшимся действием. Если пользователь пытался сохранить, сообщение должно упоминать сохранение. Если он пытался загрузить отчёт, сообщение должно говорить о загрузке отчёта.
  • Показывайте кнопку повтора только когда её безопасно нажимать. Загрузка данных обычно безопасна. Отправка платежа, сообщение или создание записи — возможно, нет.
  • Проверьте логи до релиза, а не после. Каждая ошибка должна фиксировать стабильный код, достаточно контекста экрана для трассировки проблемы и счётчик попыток, чтобы команда понимала, пытался ли пользователь один раз или шесть.
  • Задайте один вопрос для поддержки: может ли сотрудник поддержки определить проблему только по сообщению? "Что‑то пошло не так" бесполезно. "Не удалось загрузить заказ. Код ошибки ORD‑404" даёт отправную точку.

Профиль с истёкшей сессией — хороший пример. UI не должен предлагать бесконечные повторы. Он должен сказать войти снова, залогировать код ошибки авторизации и не притворяться, что экран сам себя исправит.

Тестируйте худший сценарий, а не только счастливый путь. Выключите сеть, форсируйте таймаут и нажмите повтор больше одного раза. Если экран остаётся понятным, логи читаемыми, а поддержка может понять, что провалилось — релиз в порядке.

Следующие шаги для внедрения

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

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

Практический rollout прост: спланируйте текущие состояния ошибок на выбранном экране, замените кастомные состояния общей формой, перепишите сообщения так, чтобы пользователи знали, что делать дальше, и отслеживайте повторы, падения и тикеты поддержки в первые пару недель.

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

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

Если внедрение застопорилось из‑за того, что каждый экран кажется "особенным", внешнее ревью поможет. Oleg Sotnikov на oleg.is работает со стартапами и малыми командами как Fractional CTO и советник, и подобная очистка фронтенд‑систем подходит под этот опыт. Свежий анализ UI‑паттернов, ответов API и случаев поддержки выявит пробелы гораздо быстрее, чем ещё один раунд ad‑hoc исправлений.

Ждите, пока первый экран не станет простым для переработки. Тогда паттерн готов к распространению. Переходите к следующему экрану, снова держите объём маленьким и позволяйте реальному поведению пользователей формировать версию, которую команда будет повторно использовать.