2008-08-28 2 views
4

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

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

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

Вот несколько моментов, чтобы рассмотреть следующие вопросы:

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

ответ

15

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

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

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

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

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

+0

Мой, хотя точно – 2009-03-05 09:51:28

3

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

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

1

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

help?

1

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

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

0

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

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

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

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

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

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

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

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

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

    Где-то рядом с обработчиком верхнего уровня просто обрабатывайте MessageException по-другому. Вместо того, чтобы «мы зарегистрировали вашу проблему, но на данный момент вы hosed» просто отобразите сообщение, содержащееся в 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

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

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

0

@erikson

Просто, чтобы добавить еду ваших мыслей:

Проверены по сравнению с непроверенным также debated here

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

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

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

2

См Patterns for Generation, Handling and Management of Errors

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

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

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

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

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

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

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

Смежные вопросы