Где размещать catch и логирование при обработке исключений

· · Обработка исключений, Логирование, Обработка ошибок, Проектирование, C# / .NET

Содержание

  1. Сначала вывод
  2. catch, логирование и обработка ошибок - разные вещи
    • 2.1. Перехват (catch)
    • 2.2. Логирование
    • 2.3. Обработка ошибок
    • 2.4. Трансляция исключений
  3. Таблица для первичного решения
  4. Что делать на каждом уровне цепочки вызовов
    • 4.1. Самый глубокий helper / utility / private-метод
    • 4.2. Граница внешнего I/O: Repository / Gateway / обёртки SDK
    • 4.3. Application Service / UseCase
    • 4.4. Граница UI / HTTP / Job / Message
    • 4.5. Последний обработчик необработанных исключений
    • 4.6. Взгляд на одну цепочку вызовов
  5. Разделение ожидаемых неудач и непредвиденных исключений
  6. Где и сколько раз логировать
  7. Частые ошибки
  8. Чек-лист для ревью
  9. Краткая сводная таблица
  10. Итог
  11. Справочные материалы
  12. Похожие статьи

1. Сначала вывод

  • Принцип: не перехватывать широко на глубоких слоях. Место catch смещается к границе, где можно определить единицу неудачи.
  • Логирование по умолчанию: один основной лог на одну неудачу. Если каждый слой продолжает логировать одно и то же исключение как Error, читателю становится тяжело.
  • Ответственность самого глубокого слоя - уборка, локальный откат, трансляция исключения и, если нужно, ограниченный retry. Если исключение пробрасывается дальше, обычно именно там основной лог не пишется.
  • Границы обработки - одна операция на экране, один HTTP-запрос, одно задание, обработка одного сообщения - чаще всего оказываются самым естественным местом для основного лога.
  • Ожидаемые неудачи превращаются в результат на уровне соответствующего use case. Не обязательно прокидывать всё до самого верха в виде исключения.
  • AppDomain.UnhandledException, DispatcherUnhandledException в WPF, ThreadException в WinForms, обработчик исключений ASP.NET Core, финальная обработка исключений на уровне хоста - это скорее последняя точка фиксации, чем точка восстановления.
  • OperationCanceledException из-за отмены пользователем или завершения работы обычно не считается Error.
  • Если сомневаетесь, проверяйте в таком порядке:
    1. Можно ли действительно принять решение в этом месте?
    2. Известна ли здесь единица неудачи?
    3. Можно ли здесь откатить или заново собрать состояние?
    4. Если залогировать здесь, не залогируется ли то же исключение выше?

Иными словами, основа такова: перехватывать не там, где это возможно, а там, где можно ответственно принять решение.

2. catch, логирование и обработка ошибок - разные вещи

2.1. Перехват (catch)

catch - это приём исключения с изменением дальнейшего хода обработки. Но само по себе это ещё не восстановление.

Например, даже если исключение получено в нижележащем методе, но при этом:

  • непонятно, что показать пользователю;
  • непонятно, нужно ли останавливать весь экран или достаточно провалить только текущую операцию;
  • непонятно, можно ли продолжать этот request или job,

то, скорее всего, это место не подходит для catch.

2.2. Логирование

Лог - это запись не только самого факта «произошло исключение», но и того, какая именно работа не удалась, чтобы потом можно было это отследить.

Поэтому у хорошей точки логирования обычно есть что-то из этого списка:

  • requestId / traceId
  • userId
  • orderId / fileId / batchId
  • номер обрабатываемой записи
  • какая именно операция на экране
  • какая очередь, какое сообщение

Глубокие helper-ы и общие функции часто знают технические детали, но не располагают этим контекстом. Поэтому место, знающее технические детали, и место, знающее рабочий контекст, зачастую оказываются разными.

2.3. Обработка ошибок

Под обработкой ошибок здесь понимается вот что.

  • Показ сообщения об ошибке на экране
  • Возврат 4xx / 5xx в HTTP
  • Провал только одной записи с переходом к следующей
  • Повторная инициализация подсистемы
  • Завершение процесса с расчётом на перезапуск
  • Освобождение ресурсов и безопасный выход

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

2.4. Трансляция исключений

На практике между catch и «обработкой» есть ещё одна важная задача. Это трансляция.

Например, если пропустить напрямую в UI или Controller:

  • HttpRequestException
  • IOException
  • JsonException
  • специфичные исключения драйвера БД
  • специфичные исключения SDK вендора,

то верхние слои начинают знать о деталях реализации нижних.

Поэтому на границе их преобразуют в неудачи, осмысленные для этого слоя, например:

  • «не удалось подключиться к платёжному сервису»;
  • «формат CSV повреждён»;
  • «не удалось записать в место назначения»;
  • «ответ устройства некорректен».

Здесь важно, что трансляция и логирование - не одно и то же. Если исключение просто транслируется и пробрасывается дальше, основной лог обычно не пишется.

3. Таблица для первичного решения

Проще всего сразу определить общую политику по этой таблице.

Место Базовая политика Основной лог Основная ответственность
helper / utility / private-метод По умолчанию не перехватывать широко Не пишется Уборка через finally, локальный откат, минимальное добавление контекста
Repository / Gateway / обёртка SDK Перехватывать только конкретные исключения Обычно не пишется Трансляция исключений, ограниченный retry, отбрасывание соединений и хендлов
Application Service / UseCase Превращать ожидаемые неудачи в результат При поглощении - здесь, при необходимости Определение единицы неудачи, частичный провал, решения уровня use case
Граница UI / Controller / API / Job / Message Основной приёмник непредвиденных исключений Здесь чаще всего основной лог Ответ пользователю, HTTP-ответ, переход к следующей записи, решение об abort
Обработчик необработанных исключений Последний рубеж против утечек Critical Финальная фиксация, flush, dump, путь завершения / перезапуска

Схематично это выглядит примерно так.

НетДаНетДаНетДаПроизошло исключениеМожно ли в этом месте решить retry / результат / возможность продолжения?По умолчанию не перехватывать, передать вышеЭто граница слоёв?Только локальная уборкаПри необходимости транслировать в осмысленное исключениеИзвестны ли здесь единица неудачи и рабочий контекст?Не писать основной лог, передать вышеНаписать основной лог один раз и решить, каким будет ответПри необходимости: завершение / повторная инициализация / переход к следующей записи

У этой схемы два ключевых момента.

  1. Первая причина для catch - восстановление или уборка, а не логирование.
  2. Первая причина для лога - наличие собранного рабочего контекста, а не сам факт обнаружения исключения.

4. Что делать на каждом уровне цепочки вызовов

4.1. Самый глубокий helper / utility / private-метод

Здесь базовое правило - не перехватывать широко.

Такие места, как преобразование строк, парсинг, вычисления, внутреннее форматирование, общие helper-ы, не могут определить:

  • какая это была операция на экране;
  • какой это был request;
  • допустимо ли провалить только эту попытку;
  • нужно ли закрывать весь экран целиком.

На этом слое допустимо в основном следующее.

  • Освобождение ресурсов в finally
  • Откат локального состояния, изменённого наполовину
  • Минимальное добавление контекста к сообщению исключения
  • Замена на более подходящий тип исключения
  • Уничтожение объектов, которые больше нельзя переиспользовать

И наоборот, следует избегать такого стиля.

  • catch (Exception) с возвратом null / false / пустого массива
  • Показ MessageBox в этом месте
  • Логирование как Error здесь с последующим повторным пробросом
  • «Просто продолжать», когда состояние нельзя восстановить

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

4.2. Граница внешнего I/O: Repository / Gateway / обёртки SDK

Здесь причина для catch очевидна.

Потому что именно тут наружу выходят детали реализации нижележащего слоя.

  • Исключения драйвера БД
  • Исключения HTTP-обмена
  • Исключения файлового I/O
  • Специфичные исключения COM / P/Invoke / SDK вендора
  • Исключения библиотек парсинга и сериализаторов

На этом слое выполняется, по сути, четыре вещи.

  1. Перехват конкретных исключений Не широкий Exception, а конкретные, осмысленные исключения.

  2. Трансляция в осмысленную неудачу Чтобы верхним слоям не приходилось знать внутренние детали нижних напрямую.

  3. Если нужен локальный retry - делать его здесь Но условия строгие:
    • известно, что неудача временная;
    • операция идемпотентна;
    • заданы предел попыток и стратегия ожидания;
    • итоговое поведение при провале ясно. Только когда выполняются все четыре условия.
  4. Отбрасывание повреждённых соединений и хендлов Часто безопаснее «пересоздать соединение», чем «продолжать с тем же объектом».

Политика логирования на этом слое остаётся устойчивой при таком подходе:

  • при повторном пробросе выше основной лог обычно не пишется;
  • если исключение поглощается здесь и превращается в результат, в этот момент выводятся нужные логи и метрики;
  • каждую попытку retry обрабатывают в диапазоне Debug / Information / Warning, а фиксируют весомее только финальную неудачу.

Этот слой - место трансляции, а не, как правило, место финального решения.

4.3. Application Service / UseCase

Это слой, который решает, «как именно провалится эта конкретная работа».

Например:

  • операция сохранения;
  • подтверждение заказа;
  • импорт CSV;
  • обработка одной записи батча;
  • применение одного сообщения -

это единицы, целостные как use case, и они находятся здесь.

На этом слое можно принимать такие решения.

  • Ошибка валидации - провал только этой попытки
  • NotFound соответствует 404
  • Нарушение бизнес-правила - ожидание исправления пользователем
  • Некорректная строка CSV логируется как Warning, обработка продолжается
  • Временный сбой внешнего сервиса приводит к провалу всей операции
  • Промежуточный результат отбрасывается, обработка начинается заново

Иными словами, это место, где можно определить единицу неудачи.

Этому слою подходит такая работа:

  • превращение ожидаемых неудач в Result или DTO неудачи;
  • агрегация частичных провалов;
  • определение, сколько неудач допустимо перед остановкой;
  • преобразование в коды ошибок или ключи сообщений для пользователя.

И наоборот, этому слою не стоит тащить на себя слишком много отображения UI или формирования тела HTTP-ответа. Здесь легче разделить ответственность, если слой определяет только смысл на уровне use case, а окончательное представление отдаёт границе.

4.4. Граница UI / HTTP / Job / Message

Именно здесь в большинстве приложений чаще всего оказывается точка основного лога.

Например, такие единицы:

  • одно нажатие кнопки «Сохранить» в WinForms / WPF;
  • один HTTP-запрос в ASP.NET Core;
  • одно сообщение worker-а;
  • одна запись входных данных в батче;
  • один запуск запланированного задания.

Это место знает:

  • какая была операция;
  • чья это была операция;
  • какая это по счёту запись;
  • какой request / batch / message;
  • что вернуть пользователю или вызывающей стороне при неудаче.

Поэтому здесь обычно берут на себя такие роли:

  • принимать непредвиденные исключения в одном месте;
  • писать основной лог один раз, с контекстом;
  • преобразовывать в диалог с ошибкой, HTTP 500, Problem Details, провал задания, переход к следующей записи и т. п.

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

Например, для батчей и очередей полезно мыслить в два этапа.

  • Перехват на границе одной записи Решение, допустимо ли провалить только эту запись и перейти к следующей
  • Родительский цикл не должен глушить всё подряд Если родительский цикл падает, лучше склоняться к перезапуску всего процесса

«Проваливать записи по одной и продолжать» и «родительский цикл молча живёт дальше после непредвиденного исключения» - совершенно разные вещи.

4.5. Последний обработчик необработанных исключений

Это последний рубеж. Не волшебная точка восстановления.

Типичные представители:

  • AppDomain.UnhandledException;
  • Application.DispatcherUnhandledException в WPF;
  • Application.ThreadException в WinForms;
  • middleware и обработчики исключений ASP.NET Core;
  • финальная обработка исключений в Generic Host / worker / BackgroundService.

Основная ответственность этого слоя - самое большее следующее.

  • Финальное логирование
  • Flush
  • Обеспечение пути сбора dump
  • Сохранение информации о сессии и недавнего контекста
  • Подготовка кода завершения и пути перезапуска

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

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

Есть и практические нюансы, важные именно для .NET.

  • AppDomain.UnhandledException - это событие для уведомления и фиксации необработанных исключений. Закладывать в него слишком много логики восстановления опасно.
  • В DispatcherUnhandledException WPF есть путь установить Handled = true и внешне продолжить работу, но сначала нужно определить, возможно ли восстановление.
  • ThreadException в WinForms тоже может оставить приложение в неизвестном состоянии после обработки.
  • Middleware обработки исключений ASP.NET Core нужно размещать ближе к началу конвейера, чтобы оно могло перехватывать исключения из последующих этапов.
  • Необработанное исключение в BackgroundService, начиная с .NET 6, логируется и по умолчанию приводит к остановке хоста. Иногда безопаснее остановиться и положиться на стратегию перезапуска, чем глушить всё в родительском цикле.

Особенно в десктопных приложениях существует путь «поймать необработанное исключение и продолжить работу». Но возможность продолжить и правильность продолжения - разные вещи.

4.6. Взгляд на одну цепочку вызовов

Рассмотрим, например, такой поток.

Граница UI / Controller / JobApplication Service / UseCaseDomain / бизнес-логикаRepository / Gateway / обёртка SDKБД / HTTP / файл / SDK вендора

Роли при этом распределяются примерно так.

Кнопка «Сохранить» → SaveOrderUseCasePaymentGateway → HTTP

  • PaymentGateway
    • принимает сбои соединения и некорректный формат ответа;
    • транслирует их в «не удалось подключиться к платёжному сервису» / «некорректный ответ платёжного сервиса»;
    • если нужен retry, выполняет его здесь, при определённых условиях;
    • при повторном пробросе обычно не пишет основной лог.
  • SaveOrderUseCase
    • превращает ожидаемые неудачи вроде отказа в платеже в результат;
    • трактует это как «провал только текущего подтверждения заказа»;
    • оформляет результат неудачи так, чтобы его удобно было вернуть в UI или API.
  • Обработчик кнопки UI / Controller
    • принимает непредвиденные исключения в одном месте;
    • пишет основной лог с orderId, userId, requestId;
    • преобразует его в диалоговое окно или ответ 500 / 503.
  • Обработчик необработанных исключений
    • фиксирует только то, что дошло до этой точки;
    • выполняет dump и финальный flush;
    • ставит в приоритет путь завершения, а не восстановление.

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

5. Разделение ожидаемых неудач и непредвиденных исключений

Самое важное в этой теме - не относиться ко всему как к одному и тому же «исключению».

Сначала разделим так.

Вид неудачи Первое место обработки Типичная трактовка
Недочёт валидации Граница UseCase / request Возвращается как ошибка ввода
NotFound / Conflict UseCase / Controller 404 / 409 или сообщение на экране
Отмена пользователем / завершение работы Граница операции Трактуется как отмена. Обычно не Error
Некорректная строка CSV Граница записи Фиксируется как Warning, обработка продолжается
Временный timeout, в итоге приводящий к провалу Граница I/O - граница request Возвращается как неудача после retry
NullReferenceException, нарушение инварианта Граница request / job Основной лог и ответ с ошибкой
AccessViolationException, серьёзный OutOfMemoryException, признаки повреждения на native-границе Финальная граница Critical, склонность к завершению

Ожидаемые неудачи - это неудачи, которые можно заранее предусмотреть на этапе проектирования. Непредвиденные исключения - это неудачи, после которых сомнительно, можно ли доверять состоянию дальше.

Само это разделение снижает количество таких инцидентов:

  • NotFound каждый раз логируется как Error;
  • отмена пользователем трактуется как сбой;
  • действительно опасное нарушение инварианта проскальзывает как «провал только на этот раз».

6. Где и сколько раз логировать

В проектировании логирования важнее заранее определить, кто пишет основной лог, чем то, где именно стоит catch.

Базовых правил шесть.

  1. На одну неудачу - один основной лог Error / Critical
  2. Нижние слои при необходимости занимаются трансляцией и добавлением контекста
  3. Верхняя граница пишет основной лог с единицей неудачи и рабочим контекстом
  4. Ответственность за фиксацию поглощённой неудачи несёт только тот слой, который её поглотил
  5. Ожидаемые неудачи не логируются как Error каждый раз
  6. OperationCanceledException отделяется от обычных логов сбоев

Ниже - примерная таблица точек логирования.

Ситуация Основное место логирования Ориентировочный уровень Примечание
Ошибка валидации Граница request / use case Information или без лога Это контрактная неудача, а не сбой
Отмена пользователем / shutdown Граница операции Debug / Information Обычно не Error
Временный сбой во время retry Слой, владеющий retry Debug / Warning Не шуметь до финальной неудачи
Retry исчерпан, провал Граница request / job или слой, поглощающий исключение Warning / Error Фиксировать с единицей неудачи
Непредвиденное исключение, роняющее весь request Граница request / UI / job Error Добавить requestId, userId, entityId
Класс завершения процесса Граница необработанных исключений Critical flush, dump, путь перезапуска

На практике довольно часто встречается такое дублирование логов:

  • Repository логирует Error;
  • Service логирует то же исключение как Error;
  • Controller снова логирует Error;
  • финальный обработчик необработанных исключений логирует ещё и Critical.

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

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

7. Частые ошибки

7.1. catch (Exception) глубоко в коде с возвратом null / false

Это легко теряет информацию о причине. Более того, вызывающая сторона перестаёт различать: «данных действительно не было» или «что-то сломалось по пути».

7.2. Логирование Error на каждом слое перед повторным пробросом

Самая частая причина дублирующихся логов.

  • нижние слои занимаются только трансляцией;
  • верхняя граница пишет основной лог.

При таком разделении дублирование заметно снижается.

При повторном пробросе в C# базовое правило - использовать throw;, чтобы не сломать стек вызовов.

7.3. Библиотечный слой или общий компонент напрямую показывает UI

Если общий компонент показывает MessageBox или напрямую формирует тело HTTP-ответа, страдают и переиспользуемость, и разделение ответственности. Нижние слои безопаснее ограничить задачей вернуть осмысленную неудачу.

7.4. Логирование OperationCanceledException как сбоя на уровне Error

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

7.5. Легкомысленный retry при наличии внешних побочных эффектов

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

7.6. Попытка восстановить всё в финальном обработчике необработанных исключений

Это последняя страховка. Не то место, вокруг которого стоит строить архитектуру.

Стратегию восстановления безопаснее располагать раньше - на границе request / job / подсистемы.

8. Чек-лист для ревью

При ревью обработки исключений полезно проверять в таком порядке - так меньше шансов что-то упустить.

  • Можно ли одной фразой сказать, какое решение призван принять этот catch?
  • Действительно ли в этом месте можно решить вопрос retry / результата / возможности продолжения / ответа пользователю?
  • Если залогировать здесь, не залогируется ли та же неудача как Error выше?
  • Транслируются ли специфичные для нижней реализации исключения в осмысленную неудачу на границе?
  • Можно ли здесь откатить состояние, повреждённое на середине? Если нет - трактуется ли объект как одноразовый?
  • Отделён ли OperationCanceledException от обычных сбоев?
  • Ясно ли, идёт ли речь о продолжении по записям, провале на уровне request или завершении процесса?
  • Ожидается ли от финального обработчика необработанных исключений фиксация, а не восстановление?
  • Несёт ли лог контекст единицы неудачи - requestId / userId / batchId / fileId / rowNumber?
  • Не трактуются ли «ожидаемая неудача» и «нарушение инварианта» одинаково?

Особенно эффективно в этом чек-листе каждый раз формулировать словами, что именно решает данный catch. Если на этот вопрос нет ответа, catch, как правило, либо не нужен, либо расположен слишком глубоко.

9. Краткая сводная таблица

Напоследок - предельно сжатая таблица.

Ситуация catch Лог Обработка ошибок
helper / utility По умолчанию нет Нет Нет
Repository / Gateway / обёртка SDK Только конкретные исключения Обычно без основного лога Трансляция, локальный retry, отбрасывание соединений
UseCase / Application Service Принимает ожидаемые неудачи При поглощении - по необходимости Формирование результата, частичный провал
Граница UI / Controller / request / item / job Широко принимает непредвиденные исключения Основной лог Ответ, сообщение, продолжение / abort
Обработчик необработанных исключений Только то, что дошло сюда Critical Финальная фиксация, путь завершения

Если сомневаетесь, достаточно этих пяти пунктов.

  1. На глубоких слоях широко не хватать
  2. Перехватывать на границах
  3. Основной лог - один раз
  4. Ответственность несёт слой, поглотивший исключение
  5. Последнее необработанное исключение - это фиксация и путь завершения

10. Итог

Обработка исключений - это не история про «раз перехватить можно везде, значит и перехватываем везде».

Порядок проверки в целом такой, и его достаточно.

  1. Можно ли действительно принять решение в этом месте?
  2. Известна ли здесь единица неудачи?
  3. Можно ли здесь откатить или заново собрать состояние?
  4. Не приведёт ли логирование здесь к дублированию?
  5. Это точка восстановления или последняя точка фиксации?

При такой последовательности проверки цепочку вызовов становится заметно легче упорядочить.

Особенно важны три вещи.

  • Глубокие слои - в основном трансляция и уборка
  • Границы - в основном решения и основной лог
  • Финальный обработчик необработанных исключений - в основном фиксация и путь завершения

Иначе говоря, базовое правило таково: исключения принимаются на границе, снабжаются контекстом и обрабатываются только там, где возможно восстановление.

Как только это определено, и код-ревью, и расследование инцидентов становятся заметно более предсказуемыми.

11. Справочные материалы

12. Похожие статьи

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

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

На каком слое следует перехватывать исключения?
Принцип таков: не перехватывать широко на глубоких слоях, а смещать catch к границе, где можно определить единицу неудачи. Естественными точками приёма служат границы обработки: одна операция на экране, один HTTP-запрос, одно задание, одно сообщение. Основа - перехватывать не там, где это возможно технически, а там, где можно ответственно решить вопрос retry, результата или возможности продолжения. В глубоких helper-ах и утилитах ограничиваются уборкой в finally, локальным откатом и трансляцией исключения.
Нужно ли логировать исключение на каждом слое?
На одну неудачу основной лог уровня Error / Critical должен быть один. Если Repository логирует Error, Service логирует то же исключение как Error, а Controller делает то же самое ещё раз, при одном сбое получается несколько одинаковых стек-трейсов подряд, и читателю становится тяжело. Нижние слои должны ограничиваться трансляцией и добавлением контекста, а основной лог пишется на верхней границе, где собран рабочий контекст вроде requestId и userId. Ответственность за фиксацию неудачи несёт только тот слой, который поглощает исключение и превращает его в результат.
Как разграничить ожидаемые неудачи и непредвиденные исключения?
Ожидаемые неудачи - это те, что можно заранее предусмотреть на этапе проектирования: ошибки валидации или NotFound превращаются в результат на уровне use case и обычно не логируются как Error при каждом случае. OperationCanceledException из-за отмены пользователем тоже, как правило, не считается Error. А вот нарушение инварианта вроде NullReferenceException стоит основным логом фиксировать на границе request / job и превращать в ответ с ошибкой, а AccessViolationException или серьёзный OutOfMemoryException обрабатывать как Critical, склоняясь к завершению работы. Само это разделение заметно снижает риск того, что действительно опасная неудача останется незамеченной.
Что должен делать обработчик необработанных исключений?
AppDomain.UnhandledException, DispatcherUnhandledException в WPF, ThreadException в WinForms - это не точка восстановления, а последняя точка фиксации. Основная задача здесь - финальное логирование, flush, обеспечение сбора dump, подготовка кода завершения и пути перезапуска. К этому моменту состояние уже могло быть повреждено, поэтому даже видимость продолжения работы не означает, что продолжать действительно безопасно. Стратегию восстановления безопаснее располагать раньше - на границе предыдущего request или job.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог