Общая стратегия обработки исключений для.NET

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

Итак... Как можно избежать try/catch в каждом методе, но при этом регистрировать ошибку в том месте, где она произошла?

11 ответов

Решение

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

Это означает, например, что вы должны окружить код обработчика событий блоком try / catch. Обработчик события может быть "вершиной стека". То же самое для обработчика ThreadStart или обратного вызова из асинхронного метода.

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

В случае ASP.NET вы можете разрешить ASP.NET Health Monitoring регистрировать для вас исключение.

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

Вы можете видеть все в трассировке стека - не нужно пытаться / ловить каждый метод.

Придерживайтесь нескольких правил:

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

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

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

Трассировки стека и сообщения "отказано в разрешении" может быть достаточно, чтобы вы как программист могли выяснить, что пошло не так, но моя цель - предоставить пользователю значимую информацию, такую ​​как "Не удалось открыть файл" C:\lockedfile.txt'. Отказано в доступе."

Как в:

private void DoSomethingWithFile(string filename)
{
    // Note: try/catch doesn't need to surround the whole method...

    if (File.Exists(filename))
    {
        try
        {
            // do something involving the file
        }
        catch (Exception ex)
        {
            throw new ApplicationException(string.Format("Cannot do something with file '{0}'.", filename), ex);
        }
    }
}

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

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

1) Чтобы предотвратить сбой программы и потерю информации пользователями, я делаю это

                runProgram:
                try
                {
                    container.ShowDialog();
                }
                catch (Exception ex)
                {
                    ExceptionManager.Publish(ex);
                    if (MessageBox.Show("A fatal error has occurred.  Please save work and restart program.  Would you like to try to continue?", "Fatal Error", MessageBoxButtons.YesNo) == DialogResult.Yes)
                        goto runProgram;
                    container.Close();
                }

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

2) Я улавливаю исключения только в тех методах, где я ожидаю, что что-то может пойти не так (например, время ожидания)

3) Для удобства чтения, если у вас есть блок try catch с кучей кода в секции try и кучей в секции catch, лучше извлечь этот код в метод с хорошо известным именем.

   public void delete(Page page) 
   {
      try 
      {
         deletePageAndAllReferences(page)
      }
      catch (Exception e) 
      {
         logError(e);
      }
   }
  1. Поймать и выбросить исключение, которое вы не можете обработать, - не более чем трата процессорного времени. Если вы не можете ничего сделать с исключением или с этим исключением, проигнорируйте его и позвольте вызывающему абоненту ответить на него.
  2. Если вы хотите регистрировать каждое исключение, глобальный обработчик исключений подойдет. В.NET трассировка стека является объектом; его свойства могут быть проверены, как и любой другой. Вы можете записать свойства трассировки стека (даже в строковой форме) в выбранный вами журнал.
  3. Если вы хотите убедиться, что каждое исключение перехвачено, глобальный обработчик исключений должен сделать свое дело. По сути, ни одно приложение не должно быть без него.
  4. Ваши блоки захвата должны ловить только те исключения, которые, как вы знаете, вы можете изящно восстановить. То есть, если ты можешь что-то с этим сделать, лови это. В противном случае, пусть вызывающий беспокоится об этом. Если никакие вызывающие стороны не могут ничего с этим поделать, пусть глобальный обработчик исключений перехватит его и запишет в журнал.

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

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

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

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

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

Конечно, просто повторяем: всплываем ваши исключения наверх и регистрируем трассировку стека - это лучший подход, чем вложение блоков try-catch-log-throw по всему вашему коду.

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

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

Как избежать использования метода try/catch в каждом методе, но при этом регистрировать ошибку в том месте, где она произошла?

Это зависит от хостинга env. Asp.Net, WinForms и WPF имеют разные способы захвата необработанных исключений. Но как только глобальный обработчик передает экземпляр исключения, вы можете определить точку выброса из исключения, поскольку каждое исключение включает в себя трассировку стека.

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

Обработка исключений не должна быть запоздалой мыслью. Убедитесь, что вы делаете это последовательно. Я видел, как много людей ставили широкие попытки / уловы с начала до конца каждого метода и ловили общее исключение. Люди думают, что это помогает им получить больше информации, хотя на самом деле это не так. Больше в некоторых случаях меньше, так как меньше значит больше. Я никогда не устаю от аксиомы, что "Следует использовать исключения, чтобы отметить исключительное поведение". Восстановите, если можете, и постарайтесь уменьшить количество общих исключений. Нет ничего более расстраивающего, когда вы пытаетесь устранить неполадку и видите сотни таких же исключений NullReferenceException или аналогичных, когда что-то идет не так.

Исключения реализованы так, что они не имеют затрат, если их не выбросить.

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

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