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

Я много раз видел следующий код:

try
{
    ... // some code
}
catch (Exception ex)
{
    ... // Do something
    throw new CustomException(ex);

    // or
    // throw;

    // or
    // throw ex;
}

Не могли бы вы объяснить цель повторной генерации исключения? Соответствует ли это шаблону / передовой практике в обработке исключений? (Я где-то читал, что это называется шаблоном "Caller Inform"?)

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
37
0
13 293
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Пока я не начал использовать EntLib ExceptionBlock, я использовал их для регистрации ошибок, прежде чем их выбросить. Довольно неприятно, когда вы думаете, что я мог бы справиться с ними в тот момент, но в то время было лучше, чтобы они терпели неудачу в UAT (после их регистрации), а не прикрывали ошибку потока.

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

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

Ответ принят как подходящий

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

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

Правильно ли я понял - не нужно обрабатывать повторно выброшенное исключение?

chester89 26.11.2008 14:03

Я имею в виду следующее: если у вас есть фрагмент кода, который не может обработать исключение (т.е. вы хотите, чтобы вызывающий абонент обработал его вместо этого), у вас есть два варианта: 1. не улавливать его; 2. поймать его, записать где-нибудь, а затем снова выбросить. При повторном выбросе вызывающему абоненту кажется, что его вообще не поймали.

Chris Jester-Young 27.11.2008 08:04

Как правило, «что-то делать» предполагает либо лучшее объяснение исключения (например, включение его в другое исключение), либо отслеживание информации через определенный источник.

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

Это не означает, что метод используется по чисто уважительным причинам, он часто используется, когда разработчик считает, что информация трассировки может понадобиться в какой-то момент в будущем, и в этом случае вы получите стиль try {} catch {throw;}, который является совершенно бесполезно.

Я думаю, это зависит от того, что вы пытаетесь сделать с исключением.

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

Другой подход - это подход «повторных попыток», например, счетчик ошибок сохраняется, и после определенного количества повторных попыток это единственный раз, когда ошибка отправляется вверх по стеку (это иногда делается для доступа к базе данных для вызовов базы данных, которые истекли по таймауту, или при доступе к веб-службам через медленные сети).

Тем не менее, будет множество других причин для этого.

ОСНОВНАЯ ПРИЧИНА повторной генерации исключений - оставить стек вызовов нетронутым, чтобы вы могли получить более полную картину происходящего и последовательности вызовов.

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

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

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

В основе приложения вы обычно ловите и повторно генерируете исключение, чтобы преобразовать исключение во что-то более значимое. Например, если вы пишете уровень доступа к данным и используете пользовательские коды ошибок с SQL Server, вы можете преобразовать SqlException в такие вещи, как ObjectNotFoundException. Это полезно, потому что (а) это облегчает вызывающим абонентам обработку определенных типов исключений и (б) потому, что это предотвращает детали реализации этого уровня, такие как тот факт, что вы используете SQL Server для сохранения устойчивости, просачивающейся на другие уровни, что позволяет вам легче менять вещи в будущем.

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

не могли бы вы объяснить последнюю часть? «В идеале вы хотите опубликовать ошибку где-нибудь, что операционная группа может легко отслеживать (например, в журнале событий), а также где-нибудь, что дает контекст вокруг того, где произошло исключение в потоке управления для разработчиков (обычно отслеживание)». Какие ошибки следует публиковать для операционной группы? Вы говорите о сообщениях об исключениях, которые легче понять парням, не являющимся разработчиками?

aderchox 13.03.2021 15:34

На самом деле есть разница между

throw new CustomException(ex);

и

throw;

Второй сохранит информацию о стеке.

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

Например:

try
{

}
catch (SqlException ex)
{
    switch  (ex.Number) {
        case 17:
        case 4060:
        case 18456:
           throw new InvalidDatabaseConnectionException("The database does not exists or cannot be reached using the supplied connection settings.", ex);
        case 547:
            throw new CouldNotDeleteException("There is a another object still using this object, therefore it cannot be deleted.", ex);
        default:
            throw new UnexpectedDatabaseErrorException("There was an unexpected error from the database.", ex);
    } 
}

"Их"? Неужели люди действительно не проверяют свои сообщения об ошибках? Это вроде материала с ошибкой! :-П

Chris Jester-Young 22.09.2008 12:10

Ваше первое утверждение вводит в заблуждение. throw new CustomException (ex) не приведет к потере информации о стеке, она будет сохранена в InnerException объекта CustomException. Это когда вы бросаете бывшее; что стек утерян.

Matt Howells 22.09.2008 12:53

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

Davy Landman 22.09.2008 13:09

@davy - реализация ToString должна также включать ToString innerexception, так что, как правило, она не теряется. Большинство исключений .Net framework следуют этой практике (хотя я только что нашел одно - ServiceModel.FaultException - этого не происходит). Единственная проблема в том, что вам просто нужно знать, как регистрировать .ToString. Это лучшая практика, но я редко вижу, чтобы ее обнародовали как таковую.

FastAl 21.03.2013 18:53

Я могу думать о следующих причинах:

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

  • Добавление некоторой контекстной информации к исключению. Например, вместо того, чтобы пропускать из БД простую «запись не найдена», вы можете перехватить ее и добавить «... при обработке заказа № XXX, поиск продукта YYY».

  • Выполнение некоторой очистки - закрытие файлов, откат транзакций, освобождение некоторых дескрипторов.

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

Возьмем, к примеру, метод, который загружает данные о запрошенном пользователе из текстового файла. Этот метод предполагает, что существует текстовый файл с именем, содержащим идентификатор пользователя и суффикс «.data». Когда этот файл на самом деле не существует, нет смысла генерировать исключение FileNotFoundException, потому что тот факт, что данные каждого пользователя хранятся в текстовом файле, является деталью реализации внутри метода. Таким образом, этот метод может вместо этого заключить исходное исключение в настраиваемое исключение с пояснительным сообщением.

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

Когда вы создаете собственное исключение, вот полезный контрольный список:

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

• Убедитесь, что вы реализовали три стандартных конструктора исключений.

• Убедитесь, что вы отметили свое исключение атрибутом Serializable.

• Убедитесь, что вы реализовали конструктор десериализации.

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

• Если вы добавляете какие-либо настраиваемые свойства, убедитесь, что вы реализуете и переопределяете GetObjectData для сериализации ваших настраиваемых свойств.

• Если вы добавляете какие-либо настраиваемые свойства, переопределите свойство «Сообщение», чтобы можно было добавить свои свойства в стандартное сообщение об исключении.

• Не забудьте прикрепить исходное исключение с помощью свойства InnerException вашего настраиваемого исключения.

Как упоминал Рафаль, иногда это делается для преобразования отмеченного исключения в непроверенное исключение или в проверенное исключение, которое больше подходит для API. Вот пример:

http://radio-weblogs.com/0122027/stories/2003/04/01/JavasCheckedExceptionsWereAMistake.html

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

И это обычная операция в неисключительном мире. Обычно это происходит на границе двух уровней приложения - когда функция уровня B вызывает функцию уровня C, она преобразует результат C во внутреннюю форму B.

A -- calls --> B -- calls --> C

Если это не так, то на уровне A, который вызывает уровень B, будет полный набор исключений JDK для обработки. :-)

Как также указывает принятый ответ, уровень A может даже не знать об исключении C.

Пример

Слой А, сервлет: извлекает изображение и его метаинформацию Слой B, библиотека JPEG: собирает доступные теги DCIM для анализа файла JPEG Слой C, простая БД: класс, читающий строковые записи из файла с произвольным доступом. Некоторые байты повреждены, поэтому выдается исключение, в котором говорится, что «не удается прочитать строку UTF-8 для записи 'bibliographicCitation'».

Таким образом, A не понимает значения «библиографического цитирования». Таким образом, B должен преобразовать это исключение для A в TagsReadingException, который является оболочкой для оригинала.

Другие вопросы по теме