Как мне оправиться от неконтролируемого исключения?

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

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

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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

Из шаблона Split Domain и Technical Errors

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

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

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

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

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

Таким образом, оберните исключение hibernate на границе для перехода в спящий режим с помощью исключенного исключения домена, такого как "UniqueUsernameException", и пусть это полностью перевернется к его обработчику. Убедитесь, что javadoc выбрал исключение, даже если оно не является исключением!

Ответ 4

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

помощь?

Ответ 5

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

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

Ответ 6

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

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

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

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

Ответ 7

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

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

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

  • Если мы хотим обрабатывать "уникальное нарушение имени" только путем отображения пользователю хорошего сообщения об ошибке (страницы ошибки), нет необходимости в конкретном исключении 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
}

Ответ 8

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

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

Ответ 9

@erikson

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

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

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

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

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