Вопрос

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

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

    // or
    // throw;

    // or
    // throw ex;
}

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

Это было полезно?

Решение

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

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

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

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

throw new CustomException(ex);

и

throw;

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

Но иногда вы хотите сделать исключение более «дружественным» для вашего домена приложения. Вместо того, чтобы позволить 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);
    } 
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Как упомянул Рафаль, иногда это делается для преобразования проверенного исключения в непроверяемое исключение или в проверенное исключение, которое больше подходит для 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исключение.

Пример

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top