Как использовать try catch для обработки исключений - лучшая практика

сохраняя код моего коллеги даже у того, кто утверждает, что он старший разработчик, я часто вижу следующий код:

try
{
  //do something
}
catch
{
  //Do nothing
}

или иногда они записывают информацию о регистрации в файлы журналов, например следующие try catch block

try
{
  //do some work
}
catch(Exception exception)
{
   WriteException2LogFile(exception);
}

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

Пожалуйста, дайте мне несколько советов.

Ответ 1

Моя стратегия обработки исключений:

  • Чтобы перехватить все необработанные исключения, перейдя на Application.ThreadException event, затем выберите:

    • Для приложения пользовательского интерфейса: выдать его пользователю с сообщением об извинениях (winforms)
    • Для приложения "Служба" или "Консоль": зарегистрируйте его в файле (службе или консоли).

Затем я всегда заключу каждый фрагмент кода, который выполняется извне в try/catch:

  • Все события, запущенные инфраструктурой Winforms (Load, Click, SelectedChanged...)
  • Все события, запущенные сторонними компонентами

Затем я заключу в 'try/catch'

  • Все операции , которые, как я знаю, могут не работать все время (операции ввода-вывода, вычисления с нулевым делением...). В этом случае я бросаю новый ApplicationException("custom message", innerException), чтобы отслеживать, что на самом деле произошло.

Кроме того, я стараюсь, чтобы правильно сортировать исключения. Существуют исключения, которые:

  • необходимо немедленно показать пользователю.
  • требуется некоторая дополнительная обработка, чтобы собрать вещи, когда они избегают каскадных проблем (то есть: поместите .EndUpdate в разделе finally во время заполнения TreeView)
  • пользователю все равно, но важно знать, что произошло. Поэтому я всегда регистрирую их:

    • В журнале событий
    • или в файле .log на диске

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

Я также заставляю себя попробовать:

  • Запомните ВСЕ исключения перепутаны до верхнего уровня. Нет необходимости размещать обработчики исключений всюду.
  • Функции повторного использования или глубокие вызовы не должны отображать или записывать исключения: они либо автоматически активируются, либо перезапускаются с помощью некоторых пользовательских сообщений в моих обработчиках исключений.

Итак, наконец:

Плохо:

// DON'T DO THIS, ITS BAD
try
{
    ...
}
catch 
{
   // only air...
}

Бесполезные:

// DONT'T DO THIS, ITS USELESS
try
{
    ...
}
catch(Exception ex)
{
    throw ex;
}

Вполне возможно, что попытка без улов абсолютно верна:

try
{
    listView1.BeginUpdate();

    // If an exception occurs in the following code, then the finally will be executed
    // and the exception will be thrown
    ...
}
finally
{
    // I WANT THIS CODE TO RUN EVENTUALLY REGARDLESS AN EXCEPTION OCCURED OR NOT
    listView1.EndUpdate();
}

Что я делаю на верхнем уровне:

// i.e When the user clicks on a button
try
{
    ...
}
catch(Exception ex)
{
    ex.Log(); // Log exception

    -- OR --

    ex.Log().Display(); // Log exception, then show it to the user with apologies...
}

Что я делаю в некоторых вызываемых функциях:

// Calculation module
try
{
    ...
}
catch(Exception ex)
{
    // Add useful information to the exception
    throw new ApplicationException("Something wrong happened in the calculation module :", ex);
}

// IO module
try
{
    ...
}
catch(Exception ex)
{
    throw new ApplicationException(string.Format("I cannot write the file {0} to {1}", fileName, directoryName), ex);
}

Существует много работы с обработкой исключений (Custom Exceptions), но правила thoses, которые я стараюсь иметь в виду, достаточны для простых приложений, которые я делаю.

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

// Usage:

try
{
    // boom
}
catch(Exception ex)
{
    // Only log exception
    ex.Log();

    -- OR --

    // Only display exception
    ex.Display();

    -- OR --

    // Log, then display exception
    ex.Log().Display();

    -- OR --

    // Add some user-friendly message to an exception
    new ApplicationException("Unable to calculate !", ex).Log().Display();
}

// Extension methods

internal static Exception Log(this Exception ex)
{
    File.AppendAllText("CaughtExceptions" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", DateTime.Now.ToString("HH:mm:ss") + ": " + ex.Message + "\n" + ex.ToString() + "\n");
    return ex;
}

internal static Exception Display(this Exception ex, string msg = null, MessageBoxImage img = MessageBoxImage.Error)
{
    MessageBox.Show(msg ?? ex.Message, "", MessageBoxButton.OK, img);
    return ex;
}

Ответ 2

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

Есть 3 обстоятельства, в которых использование try-catch имеет смысл.

  1. Всегда обращайтесь с известными исключениями как можно ниже. Однако, если вы ожидаете исключения, обычно лучше сначала проверить его. Например, синтаксический анализ, форматирование и арифметические исключения почти всегда лучше обрабатываются в первую очередь с помощью логических проверок, а не с помощью определенной try-catch.

  2. Если вам нужно что-то сделать для исключения (например, войти в систему или откатить транзакцию), перезапустите исключение.

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

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

try 
{
    remoteApi.Connect()
}
catch(ApiConnectionSecurityException ex) 
{
    // User security details have expired
    return false;
}

return true;

Обратите внимание, что другие исключения не обнаруживаются, так как они не ожидаются.

Теперь предположим, что вы пытаетесь что-то сохранить в базе данных. Мы должны откатить его обратно, если он потерпит неудачу, поэтому у нас есть случай 2:

try
{
    DBConnection.Save();
}
catch
{
    // Roll back the DB changes so they aren't corrupted on ANY exception
    DBConnection.Rollback();

    // Re-throw the exception, it critical that the user knows that it failed to save
    throw;
}

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

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

try
{
    // Do something
}
catch(Exception ex) 
{
    // Log exception for developers
    WriteException2LogFile(ex);

    // Display message to users
    DisplayWarningBox("An error has occurred, please contact support!");
}

Тем не менее, большинство API или UI-структур имеют общие способы выполнения случая 3. Например, ASP.Net имеет желтый экран ошибок, который выводит подробности исключений, но его можно заменить более общим сообщением в производственной среде. Следование этим рекомендациям является наилучшей практикой, потому что это экономит много кода, а также потому, что регистрация ошибок и их отображение должны приниматься при настройке, а не в жестком коде.

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

Даже случай 2 может быть заменен лучшими шаблонами, например, объемы транзакций (using блоков, которые откатывают любую транзакцию, не зафиксированную во время блока) усложняют для разработчиков неправильную модель оптимальной практики.

Например, предположим, у вас есть крупномасштабное приложение ASP.Net. Регистрация ошибок может быть через ELMAH, отображение ошибок может быть информативным YSoD локально и хорошим локализованным сообщением в производстве. Все соединения с базой данных могут осуществляться через области транзакций и using блоков. Вам не нужен ни один блок try-catch.

TL; DR: Лучшая практика - вообще не использовать блоки try-catch.

Ответ 3

Исключением является ошибка блокировки.

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

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

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

Например, если вы знаете, что некоторые целочисленные входные данные могут иметь неправильный формат, используйте int.TryParse вместо int.Parse. Есть много случаев, когда вы можете сделать это вместо того, чтобы просто сказать "если это не удастся, просто выбросить исключение".

Бросать исключения дорого.

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

  • ASP.NET: Global.asax Application_Error
  • Другие: событие AppDomain.FirstChanceException.

Моя позиция заключается в том, что локальные try/catches лучше подходят для обработки особых случаев, когда вы можете перевести исключение в другое, или если вы хотите "отключить" его для очень, очень, очень, очень, очень особого случая (ошибка библиотеки выбрасывание несвязанного исключения, которое необходимо отключить, чтобы обойти всю ошибку).

Для остальных случаев:

  • Старайтесь избегать исключений.
  • Если это невозможно: обработчики исключений первого шанса.
  • Или используйте аспект PostSharp (AOP).

Отвечая на @thewhiteambit на некоторый комментарий...

@thewhiteambit сказал:

Исключения не являются фатальными ошибками, они являются исключениями! Иногда они даже не являются ошибками, но считать их фатальными ошибками - совершенно неверное понимание того, что такое исключения.

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

  • Нет подключения к базе данных => исключение.
  • Неверный формат строки для разбора на исключение типа =>
  • Попытка разобрать JSON, и хотя ввод не является JSON => исключением
  • Аргумент null то время как объект ожидался => исключение
  • В некоторых библиотеках есть ошибка => выдает неожиданное исключение
  • Там есть сокет, и он отключается. Затем вы пытаетесь отправить сообщение => исключение
  • ...

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

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

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

Кроме того, я предлагаю всем проверить парадигму быстрого отказа, опубликованную Мартином Фаулером (и написанную Джимом Шором). Именно так я всегда понимал, как обрабатывать исключения, даже до того, как попал в этот документ некоторое время назад.

[...] считать их "Фатальными ошибками" - совершенно неверное понимание того, что такое исключения.

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

Больше ответов о проблемах @thewhiteambit

Например, в случае отсутствия соединения с базой данных программа может в исключительном случае продолжить запись в локальный файл и отправить изменения в базу данных, как только она снова станет доступной. Ваш недопустимый приведение типа String-to-Number можно попытаться снова проанализировать с языковым переводом в Exception, например, если вы попытаетесь выполнить разбор по умолчанию с английского языка на Parse ("1,5"), и вы попробуете его снова с немецким переводом, который полностью хорошо, потому что мы используем запятую вместо точки в качестве разделителя. Вы видите, что эти исключения не должны даже блокировать, им нужна только некоторая обработка исключений.

  1. Если ваше приложение может работать в автономном режиме без сохранения данных в базе данных, вы не должны использовать исключения, так как реализация потока управления с использованием try/catch рассматривается как анти-шаблон. Автономная работа - возможный вариант использования, поэтому вы реализуете поток управления, чтобы проверить, доступна ли база данных или нет, и вы не ждете, пока она не станет недоступной.

  2. Синтаксический анализ также является ожидаемым случаем (не ИСКЛЮЧИТЕЛЬНЫЙ СЛУЧАЙ). Если вы ожидаете этого, вы не используете исключения для управления потоком! , Вы получаете метаданные от пользователя, чтобы узнать, какова его/ее культура, и для этого вы используете средства форматирования! .NET также поддерживает эту и другие среды, и это исключение, поскольку следует избегать форматирования чисел, если вы ожидаете, что ваше приложение/служба будет зависеть от конкретной культуры.

Необработанное исключение обычно становится ошибкой, но само исключение не является codeproject.com/Articles/15921/Not-All-Exceptions-Are-Errors

Эта статья просто мнение или точка зрения автора.

Так как Wikipedia может быть просто мнением автора (-ов) статьи, я бы не сказал, что это догма, но проверь, что в статье Coding by исключение говорится где-то в некотором абзаце:

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

Это также говорит где-то:

Неправильное использование исключения

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

Честно говоря, я считаю, что программное обеспечение не может быть разработано без серьезного рассмотрения вариантов использования. Если вы это знаете...

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

  • У вас есть хорошее представление о том, почему может быть выбрано исключение, и вы можете реализовать определенное восстановление, например, предлагая пользователю ввести новое имя файла, когда вы поймаете объект FileNotFoundException.

    /li >
  • Вы можете создать и выбросить новое, более конкретное исключение.

int GetInt(int[] array, int index)
{
    try
    {
        return array[index];
    }
    catch(System.IndexOutOfRangeException e)
    {
        throw new System.ArgumentOutOfRangeException(
            "Parameter index is out of range.");
    }
}
  • Вы хотите частично обработать исключение, прежде чем передавать его для дополнительной обработки. В следующем примере блок catch используется для добавления записи в журнал ошибок перед повторным броском исключения.
    try
{
    // Try to access a resource.
}
catch (System.UnauthorizedAccessException e)
{
    // Call a custom error logging procedure.
    LogError(e);
    // Re-throw the error.
    throw;     
}

Я бы предложил прочитать весь раздел Исключения и обработка исключений ", а также Рекомендации по исключениям.

Ответ 6

Второй подход хорош.

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

try
{
  //do some work
}
catch(Exception exception)
{
   WriteException2LogFile(exception);//it will write the or log the error in a text file
}

Я рекомендую вам пойти на второй подход для всего вашего приложения.

Ответ 7

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

Ответ 8

За исключением, я пробую следующее:

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

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

В коде что-то вроде этого:

try{
    //Some code here
}
catch(DivideByZeroException dz){
    AlerUserDivideByZerohappened();
}
catch(Exception e){
    treatGeneralException(e);
}
finally{
    //if a IO operation here i close the hanging handlers for example
}

Ответ 9

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

Но в реальной жизни у вас может быть несколько ситуаций, когда вы хотите скрыть это

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

Ответ 10

Оставить пустой блок блокировки хуже, что нужно сделать. Если есть ошибка, лучший способ справиться с ней - это:

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

Ответ 11

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

Мой путь таков:

  • Перехватывать непослушные исключения на уровне приложения (т.е. В global.asax) для критических исключений (приложение не может быть полезным). Эти исключения я не ловлю на месте. Просто зарегистрируйте их на уровне приложения и позвольте системе выполнять свою работу.
  • Поймать "на месте" и показать пользователю некоторую полезную информацию (введен неправильный номер, не может разобрать).
  • Поймайте на месте и ничего не делайте о незначительных проблемах, таких как "Я проверю наличие обновлений в фоновом режиме, но служба не работает".

Это определенно не должно быть лучшей практикой. ;-)

Ответ 12

Для меня исключение обработки можно рассматривать как бизнес-правило. Очевидно, что первый подход неприемлем. Второй вариант лучше, и он может быть на 100% правильным, если контекст так говорит. Теперь, например, вы разрабатываете Outlook Addin. Если addin выбрасывает необработанное исключение, пользователь Outlook теперь может это знать, так как Outlook не будет уничтожать себя из-за отказа одного плагина. И вам трудно найти, что пошло не так. Поэтому второй подход в этом случае, для меня, является правильным. Помимо регистрации исключения, вы можете решить отобразить сообщение об ошибке для пользователя - я рассматриваю его как бизнес-правило.

Ответ 13

catch без каких-либо аргументов просто ест исключение и бесполезен. Что если произойдет фатальная ошибка? Там нет никакого способа узнать, что случилось, если вы используете catch без аргументов.

Оператор catch должен перехватывать более конкретные исключения, такие как FileNotFoundException а затем в самом конце вы должны перехватить Exception которое перехватит любое другое исключение, и зарегистрировать их.

Ответ 14

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

  • Бросок исключений
  • Использование стандартных типов исключений
  • Исключения и производительность

https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/exceptions

Ответ 15

Я могу сказать вам кое-что:

Фрагмент # 1 недопустим, поскольку он игнорирует исключение. (это глотает, как будто ничего не случилось).

Так что не добавляйте блок catch, который ничего не делает или просто отбрасывает.

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

Не используйте исключения для нормальной логики программы потока. Например:

например, подтверждение ввода. <- Это не действительно исключительная ситуация, скорее вы должны написать метод IsValid(myInput); проверить, является ли элемент ввода действительным или нет.

Дизайн кода, чтобы избежать исключений. Например:

int Parse(string input);

Если мы передадим значение, которое не может быть проанализировано, в int, этот метод вызовет исключение, вместо этого мы можем написать что-то вроде этого:

bool TryParse(string input,out int result); <- этот метод возвращает логическое значение, указывающее, был ли анализ успешным.

Может быть, это немного выходит за рамки этого вопроса, но я надеюсь, что это поможет вам принять правильные решения, когда речь идет о try {} catch(){} и исключениях.