Почему «бежать и бросать» считается антипаттерном? [закрыто]

89

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

Почему регистрация исключения с последующим его повторным генерированием (конечно, с сохранением исходной трассировки стека) - плохая идея, если вы не можете справиться с этим иначе?

Ману
источник
Попробуйте поискать ответ на softwareengineering.stackexchange.com
chharvey

Ответы:

129

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

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

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

арот
источник
для дальнейшей проработки см. комментарии к ответу Джеффа
Ману
8
Причина для регистрации и повторного запуска исключения может заключаться в том, чтобы сузить причину и зарегистрировать конкретное сообщение.
Майк Аргириу
12
Ответ: в текущей точке стека может быть полезная отладочная информация, которая не будет доступна в другой точке трассировки стека
rtconner
Поймать и повторно выбросить иногда полезно, если новое исключение более полезно с дополнительной информацией или более конкретным.
Borjab
1
Чтобы потратить на то, что прокомментировал @rtconner: в асинхронном коде может оказаться, что у ловца нет контекста, доступного метателю.
Нир Альфаси
55

Log-and-throw - хороший шаблон, если у объекта, улавливающего и повторно генерирующего исключение, есть основания полагать, что оно содержит информацию, которая не будет регистрироваться дальше по стеку вызовов - по крайней мере, не самым желаемым образом. Это может произойти по нескольким причинам:

  1. Исключение может быть перехвачено и повторно создано на границе уровня приложения и может содержать привилегированную информацию. Было бы плохо, если бы уровень базы данных разрешил исключение, например, «Попытка добавить повторяющийся ключ 'fnord' в поле 'users'», чтобы достичь внешнего уровня приложения (который, в свою очередь, мог бы открыть его пользователю), но это может быть полезно для внутренних частей базы данных, чтобы генерировать такое исключение, и для интерфейса приложения, чтобы поймать его, безопасно зарегистрировать и повторно генерировать несколько менее описательное исключение.
  2. Исключением может быть то, что внешний уровень, вероятно, ожидает обрабатывать без регистрации, но внутренний уровень может знать что-то, чего не знает внешний уровень, что предполагает, что ведение журнала может быть полезно. В качестве грубого примера средний уровень приложения может быть запрограммирован на попытку подключения к одному серверу, а если это не сработает, попробовать другой. Заполнение журнала приложения сообщениями «сбой подключения», когда сервер отключен для обслуживания, может быть бесполезным, тем более, что с точки зрения приложения все работало нормально. Может быть полезно перенаправить информацию о сбое подключения к ресурсу ведения журнала, связанному с серверами, который затем может фильтровать журналы, чтобы создавать отчет о том, когда сервер выходил из строя и когда он выходил из строя, в отличие от журнала каждой попытки подключения. .
суперкар
источник
4
№1 действительно является разумным универсальным вариантом использования, однако OP явно спросил о «его повторном использовании (конечно, с сохранением исходной трассировки стека)», поэтому №1 не является правильным ответом.
Джеффри Чжэн
@GeoffreyZheng: Это будет зависеть от того, будет ли безопасная запись исходной трассировки стека считаться «сохранением».
supercat
3
Что касается №1: раскрытие содержимого исключения (например, отображение e.getMessage()/ stacktrace в пользовательском интерфейсе, а также отправка его в виде ответа REST) ​​следует рассматривать как саму уязвимость, поскольку исключение времени выполнения может содержать любую конфиденциальную информацию. Что касается №2: вы можете повторно вызвать исключение, добавив всю информацию, которую должен знать клиент (+ основная причина), не нужно ничего регистрировать.
Никита Босик
@NikitaBosik: Независимо от того, должны ли исключения открываться на стороне клиента, приложения, которые это делают, достаточно распространены, поэтому принцип «глубокой безопасности» предполагает, что сообщения об исключениях следует очищать от конфиденциальной информации. Кроме того, если внешний уровень не ожидает отбрасывать определенный тип исключения без его регистрации или пересылки информации внешнему объекту, который может быть заинтересован, наличие на среднем уровне добавления информации, которую внешний уровень будет игнорировать, не поможет. что-нибудь.
суперкар
20

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

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

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

Джефф Фостер
источник
2
Я согласен с тем, что у вас должен быть один обработчик верхнего уровня, делающий это за вас. Но, на мой взгляд, этот обработчик верхнего уровня ДОЛЖЕН быть "log and throw" -ing. Итак, спор идет о КАКОЙ ТОЧКЕ «зарегистрировать и выбросить», а не о том, делать это вообще или не делать?!?
Manu
@Manu, я думаю, дело в том, что если это единственный обработчик (централизованный), это нормально. Если вы дублируете код, это не нормально. Я не думаю, что здесь есть что-то еще, кроме этого!
Джефф Фостер
5
@Manu - Что в таком случае бросает обработчик верхнего уровня? Мне кажется, что если есть что-то, что можно бросить, то это не обработчик верхнего уровня. И вы определенно не хотите повторно генерировать исключение в самой среде выполнения. Это приведет к остановке большинства приложений.
Aroth
1
@aroth: Понятно, вы говорите либо «регистрировать и обрабатывать» (если вы обработчик верхнего уровня), либо «не регистрировать и не бросать (или обрабатывать иначе)», если это не так. Спасибо что подметил это.
Manu
9

Журнал ИМО и бросок - явное нарушение принципа наименьшего неожиданности.

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

Бастиан Фойгт
источник
А как насчет журналов без ошибок?
Су Чжан
6
@SuZhang Я не совсем понимаю твой вопрос. Если ошибки нет, бросать нечего. Конечно, вы можете и должны вести журналы без ошибок.
Bastian Voigt