В настоящее время я занимаюсь проверкой кода, и следующий код заставил меня подпрыгнуть. Я вижу несколько проблем с этим кодом. Согласен ли ты со мной? Если да, то как мне объяснить коллеге, что это неправильно (упрямый тип ...)?
Код:
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;
}
}





Мантра такая:
Таким образом:
В вашем случае да, вы должны просто перехватить эти исключения и сделать что-нибудь полезное (возможно, не просто съесть их - вы можете 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 ...
Я думаю, что это нормально. Я думаю, что спор заключается между перехватом общего исключения и факторизацией нашего кода обработки. Если код журнала меньше 3 строк, я бы выбрал версию, которую я представил. Если этого больше, то версия вашего разработчика разумна.
Как правило, даже при «неизвестном» отказе правильное поведение - это не остановиться и загореться. По сути, вы не хотите, чтобы большая система или приложение аварийно завершали работу только из-за того, что произошло что-то неожиданное. ИМО, для обработчика «верхнего уровня» вполне приемлемо улавливать все и обрабатывать это соответствующим образом для приложения / системы в целом. (пакет: сообщение о неудачном результате для этой операции может быть в порядке. Пользовательский интерфейс: сообщить об ошибке пользователю (и, возможно, записать / переслать куда-нибудь, где разработчики могут ее найти, если, конечно, это действительно фатальная ошибка типа целостности данных)
Я думаю, что реальный вопрос здесь в том, почему эти определенные (очень общие) исключения следует выделять для ведения журнала здесь, а не просто передавать их вместе с остальными обработчику необработанных исключений вашего веб-сайта. (поведение которого также может быть: «зарегистрируйся и ешь»)
Что, если вы так же обработаете любое из исключений? (например, зарегистрируйте его и скажите пользователю «извините, попробуйте еще раз»)
Обычно вы должны перехватывать общие исключения в глобальном обработчике (который является идеальным местом для регистрации неожиданных исключений), но в остальном, как было сказано ранее, вы должны перехватывать определенные типы исключений в других местах, только если вы планируете что-то с ними делать. Блоки 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 Потому что он может :) Зачем ему объявлять метод, видимый для всего класса, если ему нужен только блок кода, специфичный для этого конкретного блока try-catch? Если он вам понадобится где-то еще, используйте метод. Просто как тот.
@surfen Он также может пропустить любые исключения, которые может вызвать сама запись. In-line и action действительно не ведут себя одинаково.
В C# 6.0 была добавлена поддержка фильтров исключений.
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 за «исключение еды» - худшая часть. По моему опыту, некоторые из наихудших ошибок (которые необходимо исправить) возникают из-за исключений, которые не могут распространяться (даже если они ограничены определенными типами).
Я не думаю, что в данном случае это так уж плохо, но я также делаю что-то подобное в своем коде с исключениями, которые можно безопасно игнорировать, если они будут пойманы, а остальные будут повторно выброшены. Как отмечено Ответ Майкла, наличие каждого улова, являющегося отдельным блоком, может вызвать некоторые проблемы с удобочитаемостью, которые предотвращаются при использовании этого маршрута.
Что касается того, чтобы указать на это вашему коллеге, я думаю, вам будет трудно убедить их, что это неправильный способ делать что-то - тем более, если они упрямы, - из-за потенциальных проблем с читабельностью, когда вы делаете что-то по-другому. . Поскольку эта версия по-прежнему генерирует общее исключение, которое не может быть обработано, это соответствует духу практики. Однако, если код соответствует следующему:
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);
}
Однако здесь есть три важных момента:
Обычно я за то, чтобы позволить неперехваченным исключениям просто «вывести» приложение из строя, завершив CLR. Однако, особенно в серверных приложениях, это иногда неразумно. Если один поток сталкивается с проблемой, которая считается нефатальной, нет смысла прерывать весь процесс, уничтожая все остальные запущенные запросы (например, WCF обрабатывает некоторые случаи таким образом).
принцепл должен только поймать исключение, с которым вы можете справиться. например, если вы знаете, как работать с findnotfound, вы перехватываете filenotfoundexception, иначе НЕ перехватываете его и не позволяете перебрасывать на верхний уровень.
Но надо отдать ему должное за лень. Отдельные блоки не писал, обработал одним
ifкак босс. :П