Зачем перебрасывать исключения? - PullRequest
34 голосов
/ 22 сентября 2008

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

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

    // or
    // throw;

    // or
    // throw ex;
}

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

Ответы [ 13 ]

38 голосов
/ 22 сентября 2008

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

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

24 голосов
/ 22 сентября 2008

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

throw new CustomException(ex);

и

throw;

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

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

Например:

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);
    } 
}
10 голосов
/ 22 сентября 2008

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

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

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

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

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

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

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

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

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

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

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

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

8 голосов
/ 22 сентября 2008

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

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

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

2 голосов
/ 22 сентября 2008

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

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

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

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

1 голос
/ 22 сентября 2008

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

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

1 голос
/ 22 сентября 2008

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

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

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

Будет много других причин сделать это.

1 голос
/ 22 сентября 2008

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

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

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

0 голосов
/ 07 ноября 2013

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

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

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

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

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

* ** 1 022 тысяча двадцать-один * Пример

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

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

0 голосов
/ 30 июля 2010

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

http://radio -weblogs.com / 0122027 / рассказы / 2003/04/01 / JavasCheckedExceptionsWereAMistake.html

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