Как лучше всего обрабатывать исключения на C# |
Добавил(а) microsin |
В этой статье (перевод [1]) раскрываются следующие вопросы, касающиеся обработки исключений (Exception): • Базовые понятия исключений C# (с примерами) [Что такое исключение?] Исключения (Exceptions) это тип ошибки, которая происходит при выполнении приложения. Ошибки обычно означают появление неожиданных проблем. Тогда как исключения, обработка которых организована в коде, являются ожидаемыми, они происходят в коде приложений по различным причинам. Приложения используют логику обработки исключений (exception handling) для явной поддержки кодом каких-то неординарных событий. Причины исключений могут быть самые разные - от печально известного NullReferenceException до таймаута обращения к базе данных. Анатомия исключений C#. Исключения позволяют передать управление из одной части кода в другую часть. Когда срабатывает/выбрасывается исключение (exception thrown), текущий поток выполнения кода секции try прерывается, и запускается выполнение секции catch. Обработка исключений C# осуществляется следующими ключевыми словами: try, catch, finally и throw. try - блок try инкапсулирует проверяемый на исключение регион кода. Если любая строка кода в этом блоке вызывает срабатывание исключения, то исключение будет обработано соответствующим блоком catch. Пример 1: базовый блок "try catch finally" На языке C# ключевые слова try и catch используются для определения блока проверяемого кода (блок try catch). Блок try catch помещается вокруг кода, который потенциально может выбросить исключение. Если произошло исключение, то этот блок try catch обработает исключение, гарантируя тем самым, что приложение не выдаст ошибку необработанного исключения (unhandled exception) [5], ошибки пользователя, и эта ошибка не разрушит процесс выполнения приложения. Ниже приведен простой пример метода, который выбрасывает исключение, и как нужно правильно использовать блок try catch finally для обработки ошибки. WebClient wc = null;
try { wc = new WebClient(); // загрузка web-страницы var resultData = wc.DownloadString("http://google.com"); } catch (ArgumentNullException ex) { // код, специфичный для исключения ArgumentNullException } catch (WebException ex) { // код, специфичный для исключения WebException } catch (Exception ex) { // код, специфичный для любого другого типа исключения } finally { // В любом случае, сработает это исключение или нет, // произойдет освобождение экземпляра класса WebClient. wc.Dispose(); } Ваш код обработки исключения на C# может применить несколько операторов catch, предназначенных для разных типов исключений. Это может быть очень полезным - в зависимости от того, что делает код. В предыдущем примере ArgumentNullException возникнет только тогда, когда переданный URL сайта окажется null. WebException происходит из-за широкого массива разных проблем. Перехват определенных типов исключений может помочь в их обработке. Пример 2: фильтры исключений Одна из новых функций C# версии 6 был ввод фильтров исключений (exception filters). Они дают Вам еще больше контроля над блоками catch и дальнейшей обработкой определенных исключений. Это помогает точно подстроить код под то, как Вы обрабатываете исключения, и как Вы хотите их обработать. До C# 6 Вы должны были поймать все типы WebException и обработать их. Теперь Вы можете принять решение только обработать их в определенных сценариях и позволить другому пузырю сценариев до кода который названный этим методом. Вот измененный пример с фильтрами: WebClient wc = null;
try { wc = new WebClient(); // загрузка web-страницы var resultData = wc.DownloadString("http://google.com"); } catch (WebException ex) when (ex.Status == WebExceptionStatus.ProtocolError) { // код, специально предназначенный для обработки ошибки ProtocolError } catch (WebException ex) when ((ex.Response as HttpWebResponse).StatusCode == HttpStatusCode.NotFound) { // код, специально предназначенный для обработки ошибки NotFound } catch (WebException ex) when ((ex.Response as HttpWebResponse).StatusCode == HttpStatusCode.InternalServerError) { // код, специально предназначенный для обработки InternalServerError } finally { // Этот код будет вызван в любом случае - сработало исключение, или нет. wc.Dispose(); } [Общие исключения .NET] Правильная обработка исключения критична для всего кода приложения. Имеется несколько часто используемых стандартных исключений (Common .NET Exceptions). Чаще всего стоит бояться исключения обращения по не инициализированной ссылке (null reference exception). Ниже приведен список общих ошибок, которые Вы будете наблюдать регулярно. System.NullReferenceException - наиболее общее исключение, связанное с вызовом метода, когда его объект не инициирован. [Как создать свои собственные пользовательские типы исключений] Исключения C# определены как классы, точно так же, как и любой другой объект C#. Все исключения наследуются от базового класса System.Exception. Есть много общих исключений (common exceptions), которые Вы можете использовать в своем собственном коде. Обычно разработчики используют стандартный объект ApplicationException или Exception для выбрасывания пользовательских исключений (custom exceptions). Вы также можете создать свой собственный тип исключения. Создание своих собственных пользовательских исключений C# в действительности полезно только если Вы хотите перехватить специфический тип исключений и обработать их как-то по-другому. Они также полезны для отслеживания очень специфичного типа исключений, которые Вы считаете экстремально критическими. С помощью пользовательского типа исключений Вы можете проще отслеживать ошибки Вашего приложения и организовывать для них логи с помощью инструментария контроля ошибок. Ниже приведен пример пользовательского типа исключений - ClientBillingException. Выставление счетов (Billing) это нечто такое, что Вы не захотели бы пропустить, и если такое происходит, то хотелось бы весьма определенным образом разобраться с обработкой такого исключения. С помощью пользовательского типа исключения для для такого события мы можем написать специальный код для исключения. Мы можем также мониторить наше приложение на такой специфический тип исключения, и оповещать человека по вызову, когда это событие произошло. Преимущества пользовательского типа исключений C#: • Вызов кода может осуществлять пользовательскую обработку Custom Exception Type Пример Custom Exception Type: public void DoBilling(int clientID) { Client client = _clientDataAccessObject.GetById(clientID); if (client == null) { throw new ClientBillingException(string.Format("Unable to find a client by id {0}", clientID)); } } public class ClientBillingException : Exception { public ClientBillingException(string message) : base(message) { } } [Как найти скрытые исключения .NET] Что такое First Chance Exceptions (исключения первого шанса)? Нормальная ситуация для большого количества исключений быть выброшенными, пойманными и затем проигнорированными. Внутренний код .NET Framework даже выбрасывает некоторые исключения, которые отбрасываются. Одна из функций C# это так называемые исключения первого шанса (first chance exceptions). Это позволяет Вам увидеть каждое выбрасываемое исключение .NET Exception по отдельности. Код, наподобие приведенного ниже, очень часто встречается в приложениях. Этот код может выбросить (throw) тысячи исключений в минуту, и никто никогда про это бы не узнал. Этот код из приложения, который показывал серьезные проблемы производительности из-за плохой обработки исключений. Исключения произойдут, если reader равен null, columnName равен null, columnName не существует в результатах, значение столбца было null, или если value неправильная для DateTime. Настоящее минное поле ошибок. public DateTime.GetDate(SqlDataReader reader, string columnName) { DateTime.value = null; try { value = DateTime.Parse(reader[columnName].ToString()); } catch { } return value; } Как в Visual Studio разрешить First Chance Exceptions. Когда запускаете приложение в отладчике Visual Studio, Вы можете установить Visual Studio останавливаться (break) в любой момент, когда выбрасывается C# Exception. Это может помочь найти исключения в своем коде, о которых Вы не знали, что они существуют. Чтобы получить доступ к настройкам исключений, перейдите в меню Debug -> Windows -> Exception Settings (Отладка -> Исключения..., это меню доступно при активной сессии отладчика). Под "Common Language Runtime Exceptions" Вы можете выбрать типы исключений, на которых отладчик должен автоматически поставить точку останова. Хорошей мыслью будет поставить здесь везде галочки. Как только код остановится на исключении, Вы можете указать ему игнорировать этот определенный тип исключений, если хотите. Как просмотреть все исключения с префиксом. Бесплатный Stackify .NET profiler [6] также может показать все Ваши исключения. Подробнее см. статью [7]. Решение Stackify Retrace [8] (платное) для Ваших серверов также может собирать все исключения первого шанса через .NET profiler. Без каких-либо изменений в коде или конфигурации ом может автоматически собрать и показать все исключения. Как в коде подписаться на First Chance Exceptions. Среда .NET Framework предоставляет способ подписаться на событие, чтобы запускать функцию обратного вызова (callback) в любой момент возникновения исключения. Вы можете использовать это, чтобы перехватить все исключения. Хорошей мыслью организовать потенциальную подписку на исключения, чтобы выводить информацию о них в окно отладки. Это дало бы некоторое отображение текущей ситуации в приложении без загромождения информацией Ваших лог-файлов. Обычно подписка делается один раз при старте приложения - в методе Main() консольного приложения или в коде запуска (startup) web-приложения ASP.NET. AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) => { Debug.WriteLine(eventArgs.Exception.ToString()); }; [Как лучше всего документировать и отслеживать исключения C#] Правильная обработка исключения критична для любого приложения. Ключевой компонент - создание библиотеки записи в лог исключений. Подробнее про это см. статью "C# Logging Best Practices" [9]. Лучше всего записывать исключения с использованием библиотек NLog, Serilog или log4net. Все эти три фреймворка дают Вам возможность записывать исключения в файл. Также они позволяют отправлять Ваши логи различным другим получателям - база данных, система лога Windows (Windows Event Viewer), email, или служба мониторинга ошибок (error monitoring service). Любое исключение в приложении должно быть записано, это критично для поиска проблем в Вашем коде. Также важно зарегистрировать больше дополнительных деталей по контексту возникновения исключения, что полезно для его диагностики и устранения причин ошибки. Это может быть информация о пользователе, ключевые используемые переменные и т. д. try { // Какие-то проверяемые действия ... } catch (Exception ex) { // ЭТО НАДО ЗАПИСАТЬ В ЛОГ! Log.Error(string.Format("Excellent description goes here about the exception. Happened for client {0}", _clientContext.ClientId), ex); throw; // может повторно выбросить ошибку, чтобы сообщить об исключении // пользователю, или throw можно закомментировать, чтобы игнорировать // исключение. } Почему запись в лог недостаточна. Лог исключений в файле хорошее решение, но это мало подходит для коммерческих приложений, работающих у пользователя. Если Вы не будете отслеживать каждый свой сервер ежедневно, то не узнаете про возникновение исключений. Служба мониторинга ошибок - ключевой инструмент для любой команды разработки. Она позволяет централизованно собирать все Ваши исключения. Решение [8] предоставляет для этого следующие возможности: • Централизованный лог исключений [Ссылки] 1. C# Exception Handling Best Practices site:stackify.com. |