Исключения должны быть исключительными

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

Что происходит с нашим приложением, когда возникает необработанное исключение?

Для начала давайте создадим приложение и посмотрим, что происходит, когда возникает исключение. Я создал простое приложение WinForms, которое выдает исключение при нажатии кнопки.

private void button1_Click(object sender, EventArgs e)
{
    throw new ArgumentException("Clicked the button");
}

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

Так можем ли мы предотвратить сбой приложения без необходимости заключать каждый метод в блок try-catch ? Ответ довольно хитрый. На MSDN есть интересная статья,которая объясняет это довольно хорошо — вот несколько выдержек из этой статьи.

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

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

Обработка исключений в приложении WinForms

.NET Framework предоставляет 2 события, которые позволяют нам обрабатывать исключения на уровне приложения. Обычно это первое, что я делаю, когда создаю приложение WinForms.

static class Program
{
    [STAThread]
    static void Main()
    {
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        Application.ThreadException += Application_ThreadException;

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Main());

        Application.ThreadException -= Application_ThreadException;
        AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
    }

    static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
    {
        HandleException(e.Exception);
    }

    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        HandleException((Exception) e.ExceptionObject);
    }

    static void HandleException(Exception ex)
    {
        MessageBox.Show(ex.ToString(), "An unhandled exception has occurred");
    }
}

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

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

Итак, когда мы должны обрабатывать исключения?

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

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

А теперь для другой стороны медали — когда мы должны обрабатывать исключения? Есть еще одно золотое правило, которого я пытаюсь придерживаться для обработкиисключений — ловить только те исключения, которые вы можете обработать . Чтобы уточнить, вот еще одна выдержка из статьи MSDN:

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

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

Резюме

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

Имейте в виду, что вам не нужно ломать спину, чтобы придерживаться правил, которые я изложил здесь — в конце концов, есть исключения из каждого правила! Удачного кодирования

Author: admin

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *