Отлов конкретных и общих исключений в С#

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

Вы обнаружите, что используете просто перехват универсального исключения или пытаетесь перехватить конкретное исключение и по умолчанию используете универсальное исключение, используя несколько блоков перехвата?

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

internal static bool ClearFlags(string connectionString, Guid ID)
{
    bool returnValue = false;
    SqlConnection dbEngine = new SqlConnection(connectionString);
    SqlCommand dbCmd = new SqlCommand("ClearFlags", dbEngine);
    SqlDataAdapter dataAdapter = new SqlDataAdapter(dbCmd);

    dbCmd.CommandType = CommandType.StoredProcedure;
    try
    {
        dbCmd.Parameters.AddWithValue("@ID", ID.ToString());

        dbEngine.Open();
        dbCmd.ExecuteNonQuery();
        dbEngine.Close();

        returnValue = true;
    }
    catch (Exception ex)
    { ErrorHandler(ex); }

    return returnValue;
}

Спасибо за совет

EDIT: вот предупреждение из анализа кода

Предупреждение 351 CA1031: Microsoft.Design: измените «ClearFlags (строка, Guid)», чтобы перехватить более конкретное исключение, чем «Исключение», или повторно создать исключение.


person Community    schedule 11.09.2009    source источник


Ответы (7)


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

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

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

Вы почти никогда не должны ловить исключение верхнего уровня и проглатывать его. Это связано с тем, что если вы перехватываете исключение верхнего уровня, вы на самом деле не знаете, что обрабатываете; это могло быть вызвано абсолютно чем угодно, поэтому вы почти наверняка не сможете сделать что-либо, что правильно обработает каждый отдельный случай сбоя. Вероятно, есть некоторые сбои, которые вы можете просто молча обрабатывать и проглатывать, но, просто проглатывая исключения верхнего уровня, вы также будете проглатывать целую кучу, которая действительно должна была быть выброшена вверх, чтобы ваш код мог обрабатывать выше. В вашем примере кода вы, вероятно, хотите обработать SQLException и записать + проглотить это; а затем для исключения зарегистрируйте и повторно выдайте его. Это покрывает себя. Вы по-прежнему регистрируете все типы исключений, но проглатываете только довольно предсказуемое исключение SQLException, которое указывает на проблемы с вашим SQL/базой данных.

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

person Simon P Stevens    schedule 11.09.2009
comment
@Simon: Спасибо! Я ценю ответ! Не могли бы вы рассказать мне немного больше о том, почему вы не должны ловить и проглатывать исключение верхнего уровня. В моем коде сейчас метод ErrorHandler собирает как можно больше информации об исключении и сохраняет ее в журнале событий (трассировка стека, внутреннее исключение, сообщение и т. д.). Я не уверен в последствиях перехвата/обработки исключения с вашей точки зрения. - person ; 11.09.2009
comment
Какой смысл перехватывать исключение, регистрировать его, а затем повторно вызывать? - person Ryan Lundy; 11.09.2009
comment
@Kyralessa: В нижней части стека кода в DAL все может пойти не так, что вы хотите зарегистрировать. Таким образом, вы перехватываете и регистрируете исключение верхнего уровня, но вам не следует его проглатывать, потому что вы действительно не знаете, что пошло не так (и вы рискуете проглотить что-то серьезное, такое как OutOfMemEx). Поэтому вам следует перебросить его. Если вы хотите обработать это, вы должны вместо этого поймать определенный тип исключения, который вы знаете, как обрабатывать. Если все, к чему вы можете прибегнуть, это перехват Exception, тогда вам следует повторно сгенерировать (или обернуть и сбросить повторно), чтобы позволить вышестоящему вызывающему объекту обработать его соответствующим образом. @Scott: я немного расширил свой ответ. - person Simon P Stevens; 12.09.2009
comment
@Саймон: Отлично! спасибо, расширенное объяснение очень ценится и очень помогло. - person ; 12.09.2009
comment
Конечно, вы не должны его глотать. Я хочу сказать, зачем регистрировать его, если вы все равно собираетесь его перебросить? Вы должны иметь возможность использовать иерархию исключений для перехвата любых исключений, которые вы ожидаете получить в своем DAL; но регистрация других кажется бессмысленной, поскольку они будут пойманы дальше (в конце концов, вы их повторно бросаете), и если вы правильно их повторно бросаете, у них будет полный стек вызовов, чтобы указать, где что-то пошло не так. - person Ryan Lundy; 21.09.2009
comment
(1) @Kyralessa: у нас есть давно работающее серверное приложение. Обычно он длится неделями. В течение этого времени, конечно, будут случайные тайм-ауты базы данных. Мы регистрируем их и повторно выбрасываем из DAL. В зависимости от того, что запрашивало данные, зависит от того, как они обрабатываются выше. Иногда, если это было просто синхронизированное обновление сервером его данных, тогда он просто молча игнорирует сбой (на самом деле он подсчитывает сбои, а несколько сбоев подряд генерируют более серьезную ошибку и уведомление), - person Simon P Stevens; 22.09.2009
comment
(2) но иногда, если это был конкретный пользовательский запрос данных, ему нужно как-то сообщить им об ошибке. Поскольку из DAL вы не знаете, какой тип вызывающего абонента запросил данные, мы предпочитаем всегда регистрировать их на более низком уровне. В BLL нам не нужно об этом беспокоиться, потому что мы знаем, что все ошибки DAL регистрируются. Затем мы регистрируемся в BLL только в том случае, если есть дополнительная информация, например источник запроса данных. - person Simon P Stevens; 22.09.2009
comment
(3) Я также полагаю, что ведение журнала на каждом уровне было бы полезно, если вы пытаетесь отладить обработку ошибок или вам требуется какая-либо форма инструментария для подробной трассировки. Я полагаю, это связано с конкретными требованиями. Да, в базовом клиентском приложении вы, вероятно, не будете регистрировать и повторно вызывать на любом уровне, но в многоуровневом серверном приложении различие между уровнями может быть важным, особенно при отслеживании пути выполнения ошибок. (Вау, извините за массивные посты). - person Simon P Stevens; 22.09.2009

Взгляните на эту статью Кшиштофа Цвалины, которая очень помогла мне понять, когда следует перехватывать или игнорировать исключения:

Как проектировать иерархии исключений

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

  • Ошибки использования, такие как DivideByZeroException, указывающие на ошибки в коде; вы не должны обрабатывать их, потому что их можно избежать, изменив код.
  • Логические ошибки, такие как FileNotFoundException, которые вам необходимо обработать, потому что вы не можете гарантировать их отсутствие. (Даже если вы проверите существование файла, он все равно может быть удален за долю секунды до того, как вы его прочтете.)
  • Системные сбои, такие как OutOfMemoryException, которых вы не можете избежать или устранить.
person Ryan Lundy    schedule 11.09.2009

Да,

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

Например, если вы делаете веб-запрос, вы должны сначала отловить такие вещи, как тайм-ауты и 404, а затем вы можете сообщить конечному пользователю, что он должен повторить попытку (тайм-аут) и/или проверить введенный URL-адрес.

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

person Greg B    schedule 11.09.2009

Рекомендуется избегать перехвата Exception и использования флагов в качестве возвращаемых значений.

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

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

person bryanbcook    schedule 11.09.2009

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

И для многих неизвестных/неожиданных исключений вы не должны позволять приложению продолжать работу. В общем, вы «отлавливаете» и обрабатываете только те исключения, которые были определены игрой в результате анализа метода, для которого вы кодируете предложение catch, которые этот метод фактически может создать и с которыми вы можете что-то сделать. Единственный раз, когда вы должны поймать все expcetoins (поймать Exception x), — это сделать что-то вроде его регистрации, и в этом случае вы должны немедленно повторно создать то же самое исключение (каким бы оно ни было), чтобы оно могло всплыть в стеке до некоторого общего «Необработанное исключение». Handler», который может отображать соответствующее сообщение для пользователя, а затем завершать работу приложения.

person Charles Bretana    schedule 11.09.2009

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

  1. Как упоминалось выше, если я собираю какую-то полезную информацию для регистрации, а затем повторно выбрасываю.

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

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

person Dan Bryant    schedule 04.04.2010

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

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

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

person Austin Salonen    schedule 11.09.2009