Как мне восстановиться после непроверенного исключения? - PullRequest
4 голосов
/ 29 августа 2008

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

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

Предположим, у меня есть веб-приложение, созданное с использованием Struts2 и Hibernate. Если из-за моего «действия» вспыхивает исключение, я записываю его в журнал и извиняюсь перед пользователем. Но одна из функций моего веб-приложения - создание новых учетных записей, для которых требуется уникальное имя пользователя. Если пользователь выбирает имя, которое уже существует, Hibernate выбрасывает org.hibernate.exception.ConstraintViolationException (непроверенное исключение) в кишки моей системы. Мне бы очень хотелось избавиться от этой конкретной проблемы, попросив пользователя выбрать другое имя пользователя, вместо того, чтобы дать им то же сообщение «мы зарегистрировали вашу проблему, но пока вы попались».

Вот несколько моментов для рассмотрения:

  1. Там много людей создают аккаунты одновременно. Я не хочу блокировать всю пользовательскую таблицу между «SELECT», чтобы увидеть, существует ли имя, и «INSERT», если его нет. В случае реляционных баз данных могут быть некоторые уловки, чтобы обойти это, но что меня действительно интересует, так это общий случай, когда предварительная проверка на исключение не будет работать из-за фундаментального состояния гонки. То же самое может относиться к поиску файла в файловой системе и т. Д.
  2. Учитывая склонность моего технического директора к управлению проездом, вызванную чтением технологических колонок в «Инк.», Мне нужен слой косвенного обращения к механизму персистентности, чтобы я мог выбросить Hibernate и использовать Kodo, или что угодно, не меняя ничего кроме самого нижнего уровня персистентности кода. На самом деле, в моей системе есть несколько таких уровней абстракции. Как я могу предотвратить их утечку, несмотря на непроверенные исключения?
  3. Одной из заявленных слабостей проверенных исключений является необходимость «обрабатывать» их при каждом вызове в стеке - либо объявив, что вызывающий метод их выбрасывает, либо перехватив их и обработав их. Обработка их часто означает включение их в другое проверенное исключение типа, соответствующего уровню абстракции. Так, например, на земле с проверенными исключениями реализация моей UserRegistry на основе файловой системы может перехватить IOException, в то время как реализация базы данных перехватит SQLException, но оба будут выбрасывать UserNotFoundException, который скрывает базовую реализацию. , Как мне воспользоваться неконтролируемыми исключениями, избавляя себя от бремени этого переноса на каждом уровне, не пропуская детали реализации?

Ответы [ 9 ]

15 голосов
/ 29 августа 2008

IMO, упаковка исключений (проверено или нет) имеет несколько преимуществ, которые стоят затрат:

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

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

3) Позволяет вам не зависеть от реализации кода нижнего уровня. Если вы переносите исключения и вам нужно заменить Hibernate на какой-либо другой ORM, вам нужно всего лишь изменить код обработки Hibernate. Все остальные уровни кода будут по-прежнему успешно использовать обернутые исключения и будут интерпретировать их таким же образом, даже если основные обстоятельства изменились. Обратите внимание, что это применимо, даже если Hibernate каким-либо образом изменяется (например: они переключают исключения в новой версии); это не только для оптовой замены технологии.

4) Рекомендуется использовать разные классы исключений для представления разных ситуаций. Например, у вас может быть исключение DuplicateUsernameException, когда пользователь пытается повторно использовать имя пользователя, и исключение DatabaseFailureException, когда вы не можете проверить наличие дублированных имен пользователей из-за разорванного соединения с БД. Это, в свою очередь, позволяет вам гибко и эффективно ответить на ваш вопрос («как мне восстановиться?»). Если вы получили исключение DuplicateUsernameException, вы можете предложить другое имя пользователя. Если вы получаете исключение DatabaseFailureException, вы можете позволить ему всплывать до того момента, когда он отображает пользователю страницу «не работает», и отправляет вам уведомление по электронной почте. Если у вас есть пользовательские исключения, у вас есть настраиваемые ответы - и это хорошо.

3 голосов
/ 29 августа 2008

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

Тем не менее, я думаю, что неуникальное имя пользователя не является "исключительной" ситуацией, достаточной для оправдания броска. Я бы использовал вместо этого логический аргумент возврата. Не зная о вашей архитектуре, мне трудно сказать что-то более конкретное или применимое.

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

См. Шаблоны для генерации, обработки и Управление ошибками

Из шаблона разделения домена и технических ошибок

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

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

Ошибки домена должны пройти "плавно" через технический границы. Может быть, что такие ошибки должны быть сериализованы и восстановлены чтобы это произошло. Прокси и фасады должны нести ответственность за делая это.

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

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

Итак, оберните исключение гибернации на границе в состояние гибернации с неконтролируемым исключением домена, таким как «UniqueUsernameException», и позвольте этому всплыть до самого обработчика. Убедитесь, что Javadoc выбросило исключение, даже если это не проверенное исключение!

1 голос
/ 29 августа 2008

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

Также я бы порекомендовал взглянуть на документацию Microsoft Enterprise Library Блок обработки исключений , в котором есть хорошая схема шаблонов обработки ошибок.

1 голос
/ 29 августа 2008

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

помощь

0 голосов
/ 16 сентября 2008

@ Эриксон

Просто чтобы добавить пищу к своим мыслям:

Проверено и не проверено также обсуждается здесь

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

Что касается вашей конкретной проблемы, вы должны перехватить непроверенное исключение на высоком уровне и инкапсулировать его, как говорит @Kanook в вашем собственном исключении, без отображения стека вызовов (как упомянуто @Jan Soltis)

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

0 голосов
/ 03 сентября 2008

@ Jan Проверено, а не проверено - центральная проблема здесь. Я подвергаю сомнению ваше предположение (# 3), что исключение следует игнорировать во промежуточных кадрах. Если я это сделаю, в моем высокоуровневом коде появится зависимость, зависящая от реализации. Если я заменю Hibernate, блоки catch во всем приложении придется модифицировать. Тем не менее, в то же время, если я поймаю исключение на более низком уровне, я не получу особой выгоды от использования неконтролируемого исключения.

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

0 голосов
/ 02 сентября 2008
  1. Вопрос на самом деле не связан с дебатами с проверкой или без проверки, то же самое относится к обоим типам исключений.

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

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

  4. Если мы хотим обработать «нарушение уникального имени» только путем отображения пользователю приятного сообщения об ошибке (страницы ошибки), на самом деле не требуется особая исключительная ситуация DuplicateUsernameException. Это снизит количество классов исключений. Вместо этого мы можем создать MessageException, который можно использовать во многих похожих сценариях.

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

    Где-то рядом с обработчиком верхнего уровня, просто обработайте MessageException другим способом. Вместо того, чтобы «мы зарегистрировали вашу проблему, но пока что вы попали», просто отобразите сообщение, содержащееся в исключении MessageException, без трассировки стека.

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

Код может выглядеть следующим образом

// insert the user
try {
   hibernateSession.save(user);
} catch (ConstraintViolationException e) {
   throw new MessageException("Username " + user.getName() + " already exists. Please choose a different name.");
}

В совершенно другом месте есть главный обработчик исключений

try {
   ... render the page
} catch (MessageException e) {
   ... render a nice page with the message
} catch (Exception e) {
   ... render "we logged your problem but for now you're hosed" message
}
0 голосов
/ 29 августа 2008

Вы можете перехватывать непроверенные исключения, не оборачивая их. Например, следующее допустимое Java.

try {
    throw new IllegalArgumentException();
} catch (Exception e) {
    System.out.println("boom");
}

Таким образом, в вашем действии / контроллере вы можете иметь блок try-catch вокруг логики, в которой выполняется вызов Hibernate. В зависимости от исключения вы можете отображать конкретные сообщения об ошибках.

Но я думаю, что сегодня у вас может быть Hibernate, а завтра - SleepLongerDuringWinter Framework. В этом случае вам нужно притвориться, что у вас есть собственная небольшая платформа ORM, которая охватывает стороннюю среду. Это позволит вам превратить любые специфичные для фреймворка исключения в более значимые и / или проверенные исключения, которые вы знаете, как лучше понять.

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