10 Принципов отказоустойчивости (с примерами на Javascript)
- вторник, 17 июня 2025 г. в 00:00:03
Отказоустойчивость (англ. resilience, fault tolerance) — это способность системы продолжать работу, несмотря на внутренние ошибки, сбои в зависимостях или непредвиденные ситуации.
С хорошей отказоустойчивостью интерфейс остаётся стабильным и понятным, пользователь получает предсказуемый и комфортный опыт, а сбои отдельных компонентов не приводят к сбоям всей системы.
В этой статье речь не будет идти о конкретных примерах реализации повышения отказоустойчивости. Понять то, что нужно подключать сервисы мониторинга ошибок вы можете и без меня
Хорошая отказоустойчивость начинается с мышления
Я хочу, чтобы эта фраза въелась вам в самую подкорку
Важно не просто латать ошибки по мере их появления, а комплексно подходить к решению — формировать правильное понимание, разрабатывать устойчивые подходы и строить систему, способную адекватно реагировать на возможные сбои.
Принципы описанные ниже универсальные и подойдут к большому количеству сфер, даже вне области информационных технологий. Но моя основная опора будет на сферу веб‑разработки
Ошибки случаются. Это нормально. Ненормально — когда из‑за этих ошибок пользователь сталкивается с пустым экраном, сломанной вёрсткой, непонятным поведением или вовсе не может пользоваться приложением.
Сломаться может всё:
Любой поток может завершиться с ошибкой.
Любой HTTP‑запрос может вернуть сбой.
Любая DOM‑операция может не найти элемент.
Любой объект может оказаться null
или undefined
.
Любая функция может выбросить исключение.
Любой компонент может не отрендериться.
С вашим кодом может работать неопытный специалист, который станет причиной ошибок, о которых вы не могли и подозревать
И это только начало
Предполагайте, что любая операция может выбросить ошибку — и проектируйте интерфейсы с учётом этого
Самый худший пользовательский опыт — когда пользователь ничего не понимает, и приложение выглядит так, будто «сломалось», хотя он ничего не делал.
Мы не можем гарантировать, что у пользователя будет стабильный интернет, мощный браузер, последняя версия приложения или даже адекватные данные с сервера. Но мы можем сделать так, чтобы его опыт оставался целостным, даже если что‑то пошло не так.
Цель — не закрыть героически закрыть «дыру», а дать пользователю хороший опыт
Пользователь должен получать обратную связь на свои действия — даже при сбое
Если ошибка произошла в ответ на действие пользователя и т. д. — пользователь должен получить понятный и своевременный ответ. Молчание системы или неявные сбои создают ощущение, что «что‑то сломалось».
Даже если операция не удалась, важно сообщить об этом: показать сообщение об ошибке, визуально отметить сбой, предложить повтор действия или объяснить, что делать дальше. Это сохраняет доверие и делает опыт предсказуемым.
Плох тот опыт, когда пользователю приходится раз за разом повторять одно и то же действие, которое ни к чему не ведет
Пользователь, который получает комфортный опыт обязательно к вам вернется (и наоборот)
Пример
Если данные о цене вашего продукта не загрузились, лучше показать сообщение «Не удалось загрузить цену. Для уточнения обратитесь к менеджеру», чтобы пользователь понимал ситуацию, вместо того чтобы просто скрывать блок с ценой и оставлять его в неведении. Второй вариант ухудшит пользовательский опыт
Предотвратить все ошибки невозможно. Но часто можно дать пользователю возможность восстановиться: пересоздать компонент в нужный момент, повторить запрос, очистить состояние, обновить страницу. Не надо гнаться за тем, чтобы «никогда не падало» — важно, чтобы падение не было концом сессии пользователя.
Пример с использованием Javascript
let data;
try {
data = this.getSomeData();
} catch(error) {
data = this.getErrorData(); // ✅ Восстанавливаем работу системы
}
this.doSomething(data); // ✅ Система продолжает работать даже в случае ошибки
Обработка ошибок — try-catch
, boundary-компоненты
, error reporting, повторные запросы (retry) и другие подобные механизмы — это не заплатки и не признаки слабого кода. К ним не нужно относиться как к проявлению неуверенности — это базовые инструменты отказоустойчивости, которые помогают локализовать сбои и сохранять стабильность системы.
Сильный код — не тот, который не требует обработки ошибок, а тот, который использует обработки как инструмент
Вам не должно быть стыдно, когда вы видите обработку ошибки. Но стоит задуматься если в вашем коде не зашито ни единой обработки ошибки
Закладывайте все работы с ошибками еще на этапе планирования архитектуры, определяйте критически важный функционал и рискованные места, в которых потенциально могут происходить ошибки
Хорошая архитектура не в том, чтобы ошибок не было. Ошибки были, есть и будут и от этого мы никуда никогда не уйдем. Хорошая архитектура — в изоляции ошибок. Упала одна фича — приложение в целом работает. Ошибка не должна «протекать» через уровни.
Можно перенести этот принцип в реальный мир. Если у вас в комнате начался пожар, то хорошая пожаростойкая система не даст огню выйти за пределы комнаты на остальную часть дома
Пример с использованием Javascript
function someMethod(): void {
try {
doSomeRiskyOperation();
} catch (e) {
// ✅ Обрабатываем ошибку на месте
}
}
// Вызываем внутри метода другой метод
someClosedMethod();
// ✅ Ошибка во вложенном методе не вырывается во внешний
Хорошая система не должна зависеть от того, кто с ней работает. Люди приходят и уходят, команда меняется, а код остаётся. Он должен продолжать работать стабильно даже тогда, когда его трогает человек без полного контекста, без глубокого понимания архитектуры и с ограниченным опытом.
Помимо стабильности, код должен быть:
Читаемым и предсказуемым: предпочтение простым структурам и явной логике;
Устойчивым к масштабированию: спроектирован так, чтобы его можно было безопасно расширять, не ломая существующую логику.
Основанным на надёжных паттернах. Например: Single Responsibility, Dependency Injection и тп;
Код, который не рассчитан на то, что в него зайдет менее опытный разработчик с большой вероятностью когда‑нибудь сломается
Ошибку важно не подавить, а понять и устранить её источник. Заплатки и обходы скрывают проблему, но не решают её — сбои будут возвращаться или проявляться в других местах.
Обработка должна помогать выявлять корень ошибки, а не просто снимать симптомы.
Добавление проверки в месте где не нашлись какие‑либо данные не решает проблему того, что данные не пришли.
Маскировка ошибок усложняет отладку: вместо того чтобы видеть источник сбоя, разработчик сталкивается с его последствиями в другом месте или вовсе не узнает о сбое
Если вам довелось заняться устранением ошибки — ищите причины, а не устраняйте следствие
Перехват ошибки без логирования создаёт ложное ощущение надёжности. Код перестаёт сигнализировать о сбоях, даже если они повторяются или меняются.
Важно сохранять прозрачность системы — ошибки должны оставлять след (например в виде логов), чтобы их можно было вовремя заметить и исправить.
Обработка ошибки не должна превращать проблемное место в «слепую зону»
Пример с использованием Javascript
let data;
try {
data = this.getSomeData();
} catch(error) {
data = this.getErrorData();
console.error(error); // ✅ **Сохраняем сигнал о том, что восстановление системы потребовалось**
}
this.doSomething(data);
Это нужно обдумать, это нужно понять, это нужно принять
Ошибки можно перехватить — но не всегда понять. Мы можем восстановить интерфейс, вернуть fallback‑данные или скрыть сбой от пользователя, но это не гарантирует, что приложение продолжает работать так, как задумано.
Мы не всегда знаем контекст, в котором произошла ошибка, можем неверно интерпретировать её причину или упустить побочные эффекты, которые возникли из‑за сбоя и в следствии чего можем неверно выбрать способы обработки или восстановления системы
Раскрою вам тайный тайный секрет: Даже опытные программисты сеньоры с десятилетним стажем не всегда обрабатывают ошибки верно. Так что и вам стыдиться этого не нужно
Углубляясь в повышение отказоустойчивости легко впасть в кураж и начать повышать её в каждом участке кода. Но, как и с написанием юнит тестов, не стоит забывать, что работа с отказоустойчивостью зачастую требуют дополнительного кода, вычислительных ресурсов и усложняют архитектуру.
Например:
Повторные запросы (retry) увеличивают нагрузку на сервер и могут привести к задержкам.
Логирование и мониторинг требуют подключения внешних систем, занимающих сетевые ресурсы и увеличивающих время отклика.
Показ пользователю fallback‑интерфейсов или уведомлений требует дополнительных дизайнов и тестирования.
Глубокое покрытие проекта отказоустойчивостью требует времени (и, следственно, денег, если говорить о компаниях)
Поэтому важно найти баланс
Слишком «жесткая» отказоустойчивость — может привести к чрезмерной сложности, снижению производительности и ухудшению UX из‑за частых сообщений об ошибках.
Слишком «мягкая» — пользователи могут столкнуться с поломками и потерей данных
Даже если участок кода кажется надёжным и не вызывает сомнений, это не значит, что он не может сломаться. Если функционал критически важен для работы системы, его необходимо защитить обработкой ошибок.
Речь не только о «рискованных» местах — важно предусмотреть обработку там, где сбой недопустим, даже если вероятность ошибки кажется минимальной или вовсе отсутствует.
Отказоустойчивость — не про «безошибочность», а про зрелость системы и заботе о пользователе. Это способность принимать сбои как часть реальности и превращать их в управляемые сценарии, а не катастрофы.
Сильные приложения отличает не то, что в них нет ошибок, а то, как они ведут себя при ошибках. Устойчивый интерфейс, понятные fallback‑решения, изоляция сбоев, логирование и возможность восстановиться — всё это делает систему надёжной и достойной доверия.
Важно не просто «гасить баги», а проектировать системы с учётом ошибок, вкладываясь в архитектуру, мышление и процессы.
Так мы создаём не только устойчивый код, но и уверенный, предсказуемый пользовательский опыт — даже тогда, когда всё идёт не по плану.