Где следует обрабатывать исключения в сложном проекте?

Ответ

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

Ключевые принципы:

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

    • Низкий уровень (DAL, инфраструктура): Ловите технические исключения (например, SqlException, IOException), логируйте их и преобразуйте в доменные или пользовательские исключения более высокого уровня.
      // Пример в репозитории
      public Customer GetCustomer(int id)
      {
      try
      {
          // ... вызов базы данных
      }
      catch (SqlException ex)
      {
          _logger.LogError(ex, "Ошибка БД при получении клиента {Id}", id);
          throw new DataAccessException("Не удалось загрузить данные клиента", ex);
      }
      }
  2. Используйте глобальные обработчики (Middleware/фильтры) для непредвиденных исключений.

    • В ASP.NET Core — UseExceptionHandler middleware или фильтры исключений. Их задача — залогировать критическую ошибку и вернуть пользователю общий форматированный ответ (например, 500 Internal Server Error), не раскрывая деталей реализации.
  3. Не обрабатывайте исключения, которые не можете обработать осмысленно. Иногда правильнее позволить исключению всплыть наверх к глобальному обработчику, чем "глушить" его пустым catch.

  4. Разделяйте бизнес-ошибки и системные исключения. Для ожидаемых нарушений бизнес-правил ("недостаточно средств", "пользователь не найден") предпочтительнее использовать возврат результата с ошибкой (например, через Result<T> или ValidationProblem), а не механизм исключений для контроля потока выполнения.

Ответ 18+ 🔞

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

Смотри, представь себе многоэтажку. На первом этаже у тебя подвал — это база данных, файловая система, всякая низкоуровневая хуйня. На чердаке — пользовательский интерфейс. Так вот, если в подвале прорвало трубу, не надо бежать на чердак и орать «У нас потоп!». Ты должен на уровне подвала сказать: «Э, ребята, тут вода, разбирайтесь». А уже на верхних этажах решат — то ли ведрами вычерпывать, то ли эвакуировать жильцов.

Вот основные правила, чтобы не облажаться:

  1. Лови там, где можешь что-то сделать. Если ты в репозитории и база данных плюнула в тебя SqlException, ты не должен просто пробросить эту ошибку наверх. Наверху не поймут, что это за «Timeout expired» такой. Ты должен её обернуть во что-то осмысленное для твоего домена.

    // Допустим, в репозитории
    public Customer GetCustomer(int id)
    {
        try
        {
            // ... лезем в базу
        }
        catch (SqlException ex) // Ловим конкретную техническую хрень
        {
            _logger.LogError(ex, "База данных взбунтовалась при запросе клиента {Id}", id);
            // Превращаем в своё, понятное исключение
            throw new DataAccessException("Не удалось загрузить клиента из-за проблем с БД", ex);
        }
    }

    Теперь на уровень выше поймают не абракадабру от SQL-драйвера, а внятное DataAccessException. Уже лучше.

  2. Поставь глобальную сетку. Это как пожарная система в здании. В ASP.NET Core — это UseExceptionHandler. Его задача — поймать ВСЁ, что долетело до самого верха и не было обработано. Что он делает? Пишет в лог всё, включая трассировку стека (потому что тебе, разработчику, это надо), а пользователю показывает какую-нибудь вежливую заглушку: «Упс, что-то пошло не так, мы уже в курсе». Никаких деталей, иначе любой проходимец узнает про твою систему больше, чем ты сам.

  3. Не глуши всё подряд пустым catch. Это, блядь, главная болезнь! Видишь try { ... } catch { } — бей по рукам. Если ты поймал исключение и не знаешь, что с ним делать (не можешь восстановить работу, не можешь преобразовать, не залогировал), то НЕ ЛОВИ ЕГО. Пусть летит наверх к глобальному обработчику. Заткнуть его и сделать вид, что ничего не было — это гарантировать, что программа будет работать неправильно, а ты об этом даже не узнаешь. Пиздец, а не подход.

  4. Разделяй ошибки бизнеса и аварии системы. Это важно, ёпта! Если пользователь пытается снять больше денег, чем есть на счету — это не исключение, это нормальная бизнес-ситуация. Не надо кидать InsufficientFundsException для контроля потока. Лучше верни объект OperationResult с флагом IsSuccess и сообщением «Недостаточно средств». Исключения — это для НЕНОРМАЛЬНЫХ, аварийных ситуаций (диск полный, сеть отвалилась, конфиг кривой). Использовать исключения для обычной логики — это как молотком гвозди забивать: вроде работает, но стена вся в дырах, и соседи жалуются.

Итог такой: выстраивай оборону слоями. На нижнем — лови технический мусор и превращай во что-то осмысленное. В середине — обрабатывай то, что можно исправить. А на самом верху — лови всё остальное, логируй и показывай юзеру, что ты не свинья, а всё под контролем. И перестань бояться, что что-то упадёт. Лучше упадёт с понятной ошибкой в логе, чем будет тихо и непредсказуемо глючить.