Это плохая практика для перехвата неспецифического исключения, такого как System.Exception? Почему?

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

  • Поймать общее исключение (Exception ex)
  • Использование «if (ex is something)» вместо другого блока catch
  • Едим SoapException, HttpException и WebException. Но если веб-служба выйдет из строя, делать нечего.

Код:

try
{
    // Call to a WebService
}
catch (Exception ex)
{
    if (ex is SoapException || ex is HttpException || ex is WebException)
    {
        // Log Error and eat it.
    }
    else
    {
        throw;
    }
}

Но надо отдать ему должное за лень. Отдельные блоки не писал, обработал одним if как босс. :П

Nikhil Wagh 06.03.2020 15:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
13
1
7 575
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

Мантра такая:

  • Вы должны ловить исключения, только если вы можете правильно с ними справиться

Таким образом:

  • Вы не должны ловить генерала исключения.

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

Ваш кодировщик использует throw (не throw ex), который является хорошо.

Вот как вы можете поймать несколько конкретных исключений:

try
{
    // Call to a WebService
}
catch (SoapException ex)
{
    // Log Error and eat it
}
catch (HttpException ex)
{
    // Log Error and eat it
}
catch (WebException ex)
{
    // Log Error and eat it
}

Это в значительной степени эквивалентно тому, что делает ваш код. Ваш разработчик, вероятно, сделал это таким образом, чтобы избежать дублирования блоков «log error and eat it».

Я полностью согласен с тобой. Но мне нужна веская причина, почему бы не использовать "if (exception is" в блоке Catch ...

Martin 09.01.2009 02:12

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

Michael Haren 09.01.2009 02:23

Как правило, даже при «неизвестном» отказе правильное поведение - это не остановиться и загореться. По сути, вы не хотите, чтобы большая система или приложение аварийно завершали работу только из-за того, что произошло что-то неожиданное. ИМО, для обработчика «верхнего уровня» вполне приемлемо улавливать все и обрабатывать это соответствующим образом для приложения / системы в целом. (пакет: сообщение о неудачном результате для этой операции может быть в порядке. Пользовательский интерфейс: сообщить об ошибке пользователю (и, возможно, записать / переслать куда-нибудь, где разработчики могут ее найти, если, конечно, это действительно фатальная ошибка типа целостности данных)

user645280 11.07.2014 18:01

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

user645280 11.07.2014 18:11

Что, если вы так же обработаете любое из исключений? (например, зарегистрируйте его и скажите пользователю «извините, попробуйте еще раз»)

The Student 17.01.2017 23:50

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

Проблема с перехватом и повторным генерированием одного и того же исключения заключается в том, что, хотя .NET делает все возможное, чтобы сохранить трассировку стека неповрежденной, она всегда заканчивается модификацией, что может затруднить отслеживание того, откуда действительно произошло исключение (например, исключение номер строки, скорее всего, будет являться строкой оператора re-throw, а не строкой, в которой изначально возникло исключение). Здесь есть масса дополнительной информации о разнице между захватом / повторным выбросом, фильтрацией и отсутствием захвата.

Когда есть повторяющаяся логика, вам действительно нужен фильтр исключений, чтобы вы могли ловить только те типы исключений, которые вам интересны. VB.NET имеет эту функциональность, но, к сожалению, C# нет. Гипотетический синтаксис может выглядеть так:

try
{
    // Call to a WebService
}
catch (Exception ex) if (ex is SoapException || ex is HttpException || /* etc. */)
{
    // Log Error and eat it
}

Поскольку вы не можете этого сделать, я предпочитаю использовать лямбда-выражение для общего кода (вы можете использовать delegate в C# 2.0), например.

Action<Exception> logAndEat = ex => 
    {  
        // Log Error and eat it
    };

try
{
    // Call to a WebService
}
catch (SoapException ex)
{
    logAndEat(ex);
}
catch (HttpException ex)
{
    logAndEat(ex);
}
catch (WebException ex)
{
    logAndEat(ex);
}

Мне любопытно, почему вы используете лямбда-выражение, а не объявляете частный метод. То есть почему «Action <Exception> logAndEat» вместо «private void LogAndEat (Exception ex)»?

Ben Robbins 20.08.2010 06:49

@Ben Robbins Потому что он может :) Зачем ему объявлять метод, видимый для всего класса, если ему нужен только блок кода, специфичный для этого конкретного блока try-catch? Если он вам понадобится где-то еще, используйте метод. Просто как тот.

surfen 18.01.2012 03:41

@surfen Он также может пропустить любые исключения, которые может вызвать сама запись. In-line и action действительно не ведут себя одинаково.

user645280 11.07.2014 18:03

В C# 6.0 была добавлена ​​поддержка фильтров исключений.

Daniel Rose 19.10.2015 14:55

I am currently doing a code review and the following code made me jump. I see multiple issues with this code. Do you agree with me?

Не совсем, см. Ниже.

  • Catch a generic exception (Exception ex)

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

  • The use of "if (ex is something)" instead of having another catch block

Чистый эффект блока catch заключается в том, что фактически обрабатываются только SoapException, HttpException и т. д., А все другие исключения распространяются вверх по стеку вызовов. Я полагаю, что с точки зрения функциональности это то, что должен делать код, так что здесь тоже нет проблем.

Однако с точки зрения эстетики и удобочитаемости я бы предпочел несколько блоков catch вместо «if (ex is SoapException || ..)». После рефакторинга кода общей обработки в метод, несколько блоков catch лишь немного сложнее набирать текст и их легче читать для большинства разработчиков. Кроме того, финальный бросок легко не заметить.

  • We eat SoapException, HttpException and WebException. But if the Web Service failed, there not much to do.

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

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

user645280 11.07.2014 18:06

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

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

try
{
  // Do some work
}
catch (Exception ex)
{
  if (ex is SoapException)
  {
    // SoapException specific recovery actions
  }
  else if (ex is HttpException)
  {
    // HttpException specific recovery actions
  }
  else if (ex is WebException)
  {
    // WebException specific recoery actions
  }
  else
  {
    throw;
  }
}

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

Иногда это единственный способ обработать сценарии «перехвата всех исключений», фактически не перехватывая каждое исключение.

Я думаю, что иногда, скажем, низкоуровневый код фреймворка / времени выполнения должен гарантировать, что никакое исключение никогда не исчезнет. К сожалению, код фреймворка также не может знать, какие исключения вызываются кодом, выполняемым потоком.

В этом случае возможный блок catch может выглядеть так:

try
{
   // User code called here
}
catch (Exception ex)
{
   if (ExceptionIsFatal(ex))
     throw;

   Log(ex);
}

Однако здесь есть три важных момента:

  1. Это не для каждой ситуации. В обзоре кода мы очень разборчивы в отношении мест, где это действительно необходимо и, следовательно, разрешено.
  2. Метод ExceptionIsFatal () гарантирует, что мы не едим исключения, которые никогда не следует проглатывать (ExecutionEngineException, OutOfMemoryException, ThreadAbortException и т. д.)
  3. То, что проглочено, тщательно регистрируется (журнал событий, log4net, YMMV).

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

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

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