javascript

10 Принципов отказоустойчивости (с примерами на Javascript)

  • вторник, 17 июня 2025 г. в 00:00:03
https://habr.com/ru/articles/918574/

Отказоустойчивость (англ. resilience, fault tolerance) — это способность системы продолжать работу, несмотря на внутренние ошибки, сбои в зависимостях или непредвиденные ситуации.

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

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

Хорошая отказоустойчивость начинается с мышления

Я хочу, чтобы эта фраза въелась вам в самую подкорку

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

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

10 Принципов отказоустойчивости

1. Сломаться может всё, в любой момент и в любом месте

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

Сломаться может всё:

  • Любой поток может завершиться с ошибкой.

  • Любой HTTP‑запрос может вернуть сбой.

  • Любая DOM‑операция может не найти элемент.

  • Любой объект может оказаться null или undefined.

  • Любая функция может выбросить исключение.

  • Любой компонент может не отрендериться.

  • С вашим кодом может работать неопытный специалист, который станет причиной ошибок, о которых вы не могли и подозревать

И это только начало

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

2. Пользовательский опыт — высший приоритет

Самый худший пользовательский опыт — когда пользователь ничего не понимает, и приложение выглядит так, будто «сломалось», хотя он ничего не делал.

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

Цель — не закрыть героически закрыть «дыру», а дать пользователю хороший опыт

Пользователь должен получать обратную связь на свои действия — даже при сбое

Если ошибка произошла в ответ на действие пользователя и т. д. — пользователь должен получить понятный и своевременный ответ. Молчание системы или неявные сбои создают ощущение, что «что‑то сломалось».

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

Плох тот опыт, когда пользователю приходится раз за разом повторять одно и то же действие, которое ни к чему не ведет

Пользователь, который получает комфортный опыт обязательно к вам вернется (и наоборот)

  • Пример

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

3. Сбой - один из этапов работы приложения, а не конец

Предотвратить все ошибки невозможно. Но часто можно дать пользователю возможность восстановиться: пересоздать компонент в нужный момент, повторить запрос, очистить состояние, обновить страницу. Не надо гнаться за тем, чтобы «никогда не падало» — важно, чтобы падение не было концом сессии пользователя.

  • Пример с использованием Javascript

    let data;
    try {
        data = this.getSomeData();
    } catch(error) {
        data = this.getErrorData(); // ✅ Восстанавливаем работу системы
    }
    
    this.doSomething(data); // ✅ Система продолжает работать даже в случае ошибки
    

4. Обработка ошибок — это часть архитектуры, а не заплатки

Обработка ошибок — try-catch, boundary-компоненты, error reporting, повторные запросы (retry) и другие подобные механизмы — это не заплатки и не признаки слабого кода. К ним не нужно относиться как к проявлению неуверенности — это базовые инструменты отказоустойчивости, которые помогают локализовать сбои и сохранять стабильность системы.

Сильный код — не тот, который не требует обработки ошибок, а тот, который использует обработки как инструмент

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

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

5. Ошибки должны быть изолированы

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

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

  • Пример с использованием Javascript

    function someMethod(): void {
        try {
          doSomeRiskyOperation();
        } catch (e) {
          // ✅ Обрабатываем ошибку на месте
        }
    }
    
    // Вызываем внутри метода другой метод
    someClosedMethod();
    // ✅ Ошибка во вложенном методе не вырывается во внешний
    

6. Код должен жить дольше своего автора

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

Помимо стабильности, код должен быть:

  • Читаемым и предсказуемым: предпочтение простым структурам и явной логике;

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

  • Основанным на надёжных паттернах. Например: Single Responsibility, Dependency Injection и тп;

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

7. Обработка ошибки должна устранять причины, а не затыкать последствия

image.png
trycatchman.png

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

Обработка должна помогать выявлять корень ошибки, а не просто снимать симптомы.

Добавление проверки в месте где не нашлись какие‑либо данные не решает проблему того, что данные не пришли.

Маскировка ошибок усложняет отладку: вместо того чтобы видеть источник сбоя, разработчик сталкивается с его последствиями в другом месте или вовсе не узнает о сбое

Если вам довелось заняться устранением ошибки — ищите причины, а не устраняйте следствие

Перехват ошибки без логирования создаёт ложное ощущение надёжности. Код перестаёт сигнализировать о сбоях, даже если они повторяются или меняются.

Важно сохранять прозрачность системы — ошибки должны оставлять след (например в виде логов), чтобы их можно было вовремя заметить и исправить.

Обработка ошибки не должна превращать проблемное место в «слепую зону»

  • Пример с использованием Javascript

    let data;
    try {
        data = this.getSomeData();
    } catch(error) {
        data = this.getErrorData();
        console.error(error); // ✅ **Сохраняем сигнал о том, что восстановление системы потребовалось**
    }
    
    this.doSomething(data);
    

8. Не все ошибки удаётся обработать правильно

Это нужно обдумать, это нужно понять, это нужно принять

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

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

Раскрою вам тайный тайный секрет: Даже опытные программисты сеньоры с десятилетним стажем не всегда обрабатывают ошибки верно. Так что и вам стыдиться этого не нужно

9. Баланс отказоустойчивости — не защищай абсолютно всё

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

Например:

  • Повторные запросы (retry) увеличивают нагрузку на сервер и могут привести к задержкам.

  • Логирование и мониторинг требуют подключения внешних систем, занимающих сетевые ресурсы и увеличивающих время отклика.

  • Показ пользователю fallback‑интерфейсов или уведомлений требует дополнительных дизайнов и тестирования.

  • Глубокое покрытие проекта отказоустойчивостью требует времени (и, следственно, денег, если говорить о компаниях)

Поэтому важно найти баланс

Слишком «жесткая» отказоустойчивость — может привести к чрезмерной сложности, снижению производительности и ухудшению UX из‑за частых сообщений об ошибках.

Слишком «мягкая» — пользователи могут столкнуться с поломками и потерей данных

10. Даже если ошибке взяться негде — критичный функционал должен быть защищён

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

Речь не только о «рискованных» местах — важно предусмотреть обработку там, где сбой недопустим, даже если вероятность ошибки кажется минимальной или вовсе отсутствует.

Заключение

Отказоустойчивость — не про «безошибочность», а про зрелость системы и заботе о пользователе. Это способность принимать сбои как часть реальности и превращать их в управляемые сценарии, а не катастрофы.

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

Важно не просто «гасить баги», а проектировать системы с учётом ошибок, вкладываясь в архитектуру, мышление и процессы.

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