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

StackOverflow https://stackoverflow.com/questions/33497

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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

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

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

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

Решение

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

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

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

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

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

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

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

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

Видеть Шаблоны для генерации, обработки и управления ошибками

Из шаблона «Раскол домена и технические ошибки»

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

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

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

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

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

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

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

помощь?

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

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

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

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

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

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

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

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

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

  4. Если мы хотим обработать «нарушение уникального имени» только путем отображения пользователю красивого сообщения об ошибке (страницы ошибки), на самом деле нет необходимости в конкретном исключении DuplicationUsernameException.Это позволит снизить количество классов исключений.Вместо этого мы можем создать 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
}

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

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

@erikson

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

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

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

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

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

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