[Программирование, C#] Лучшие практики обработки исключений в C# (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В рамках скорого старта курса "C# Developer. Professional" подготовили для вас перевод материала.
Приглашаем также всех желающих на бесплатный демо-урок «DI-контейнеры для C#». На этом занятии мы:
1) Разберемся с тем, что такое принцип DI и зачем он нужен;
2) Научимся применять DI без использования контейнеров;
3) Рассмотрим два популярных DI-контейнеры для C#: Windsor и Autofac, разберем их плюсы и минусы;
4) Научимся регистрировать зависимости, управлять их жизненным циклом, применять инъекцию зависимостей.
Я плавно приближаюсь к своему двадцатилетнему юбилею в технической индустрии. На протяжении этих лет я своими глазами повидал почти все анти-паттерны обработки исключений (да что уж там, и я сам тоже совершал ошибки). В этой статье я собрал собственные лучшие практики работы с исключениями в C#.Не генерируйте исключения повторноЯ натыкаюсь на это снова и снова. Люди оказываются сбиты с толку тем, что исходный стек трейс «волшебным образом» исчезает при обработке ошибок. Чаще всего это вызвано повторной генерацией исключений. Давайте посмотрим на пример, в котором у нас есть вложенные try/catch:
try
{
try
{
// Вызов какого-либо кода, который может сгенерировать исключение SpecificException
}
catch (SpecificException specificException)
{
log.LogError(specificException, "Specific error");
}
// Вызов какого-либо кода
}
catch (Exception exception)
{
log.LogError(exception, "General erro");
}
Как вы, наверное, уже догадались, внутренний try/catch перехватывает, регистрирует и проглатывает исключение. Чтобы пробросить SpecificException в глобальный блок catch для его обработки, вам нужно пробросить его в стек. Вы можете сделать следующее:
catch (SpecificException specificException)
{
// ...
throw specificException;
}
Или так:
catch (SpecificException specificException)
{
// ...
throw;
}
Основное отличие здесь состоит в том, что в первом примере повторно генерируется SpecificException, что приводит к сбросу стек трейса исходного исключения, в то время как второй пример сохраняют все детали исходного исключения. Почти всегда предпочтительнее использовать второй пример.Декорируйте исключенияЯ достаточно редко вижу реализацию этой рекомендации на практике. Все исключения расширяют Exception, в котором есть словарь Data. Словарь можно использовать для включения дополнительной информации об ошибке. Отображается ли эта информация в вашем логе, зависит от того, какой фреймворк логирования и хранилище вы используете. В elmah.io записи Data отображаются на вкладке Data.Информацию в словарь Data вносится посредством добавьте пар ключ/значение:
var exception = new Exception("En error happened");
exception.Data.Add("user", Thread.CurrentPrincipal.Identity.Name);
throw exception;
В этом примере я добавляю ключ с именем user с потенциальным именем пользователя, хранящимся в потоке.Вы также можете декорировать исключения, сгенерированные сторонним кодом. Добавьте try/catch:
try
{
service.SomeCall();
}
catch (Exception e)
{
e.Data.Add("user", Thread.CurrentPrincipal.Identity.Name);
throw;
}
Код перехватывает любые исключения, генерируемые методом SomeCall, и добавляет в них имя пользователя. Посредством добавления ключевого слова throw в блок catch исходное исключение пробрасывается дальше по стеку.Перехватывайте в первую очередь наиболее специфические исключенияВероятнее всего, у вас есть где-то код, похожий на этот:
try
{
File.WriteAllText(path, contents);
}
catch (Exception e)
{
logger.Error(e);
}
Простой перехват Exception и логирование его в предпочитаемом фреймворке быстро реализуются и справляются со своей задачей. Большинство библиотек, доступных в .NET, могут генерировать ряд различных исключений, и у вас может даже уже быть похожий шаблон в вашей кодовой базе. Перехват нескольких исключений в диапазоне от наиболее до наименее специфической ошибки — отличный способ определить, как вы хотите обрабатывать каждый конкретный тип исключения.В следующем примере я четко демонстрирую понимание, какие исключения следует ожидать и как поступать с каждым конкретным типом:
try
{
File.WriteAllText(path, contents);
}
catch (ArgumentException ae)
{
Message.Show("Invalid path");
}
catch (DirectoryNotFoundException dnfe)
{
Message.Show("Directory not found");
}
catch (Exception e)
{
var supportId = Guid.NewGuid();
e.Data.Add("Support id", supportId);
logger.Error(e);
Message.Show($"Please contact support with id: {supportId}");
}
Перехватывая ArgumentException и DirectoryNotFoundException перед перехватом общего Exception, я могу показать пользователю специализированное сообщение. В этих сценариях я не регистрирую исключение, поскольку пользователь может быстро исправить ошибки. В случае Exception я генерирую support id, регистрирую ошибку (используя декораторы, как показано в предыдущем разделе) и показываю сообщение пользователю.Обратите внимание, что, хотя приведенный выше код служит для объяснения порядка обработки исключений, реализация потока управления, используя исключения подобным образом — практика не очень хорошая. Это прекрасная подводка к следующему совету:Старайтесь избегать исключенийМожет показаться очевидным, что нужно избегать исключений. Но многих методов, генерирующих исключение, можно избежать с помощью защитного программирования.Одно из самых распространенных исключений — NullReferenceException. В некоторых случаях вы можете разрешить null, но забыть проверить на null. Вот пример, который генерирует NullReferenceException:
Address a = null;
var city = a.City;
Доступ к a выбрасывает исключение. Хорошо, но представьте, что a предоставляется в качестве параметра.Если вы хотите разрешить city с нулевым значением, вы можете избежать исключения, используя null-condition оператор:
Address a = null;
var city = a?.City;
Добавляя ? при доступе к a C# автоматически обрабатывает сценарий, в котором адрес равен null. В этом случае переменной city будет присвоено значение null.Другой распространенный пример исключений — это анализ чисел или логических значений. В следующем примере будет сгенерировано FormatException:
var i = int.Parse("invalid");
Строка invalid не может быть распаршена в виде целого числа. Чтобы не оборачивать это в try/catch, int предоставляет интересный метод, который вы, вероятно, уже использовали 1000 раз:
if (int.TryParse("invalid", out int i))
{
}
В случае, если invalid может быть распаршена как int, TryParse возвращает true и помещает распаршенное значение в переменную i. Еще одно исключение удалось избежать.Создавайте пользовательские исключенияЗабавно вспоминать, как я был Java-программистом (когда .NET находился в стадии бета-тестирования). Мы создавали собственные пользовательские исключения для всего чего угодно. Возможно, это происходило из-за более явной реализации исключений в Java, но я не вижу этого в .NET и C#. Создавая пользовательское исключение, у вас гораздо больше возможностей для перехвата определенных исключений, как уже было показано. Вы можете декорировать свое исключение пользовательскими переменными, не беспокоясь о том, поддерживает ли ваш логгер словарь Data:
public class MyVerySpecializedException : Exception
{
public MyVerySpecializedException() : base() {}
public MyVerySpecializedException(string message) : base(message) {}
public MyVerySpecializedException(string message, Exception inner) : base(message, inner) {}
public int Status { get; set; }
}
Класс MyVerySpecializedException (возможно, это не то имя класса, которое вы должны использовать в качестве примера :D) реализует три конструктора, которые должен иметь каждый класс исключения. Кроме того, я добавил свойство Status в качестве примера дополнительных данных. Это позволит нам написать такой код:
try
{
service.SomeCall();
}
catch (MyVerySpecializedException e) when (e.Status == 500)
{
// Do something specific for Status 500
}
catch (MyVerySpecializedException ex)
{
// Do something general
}
Используя ключевое слово when, я могу перехватить MyVerySpecializedException, когда значение свойства Status равно 500. Все остальные сценарии попадут в общий catch MyVerySpecializedException.Логируйте исключенияЭто кажется таким очевидным. Но я видел слишком много ошибок в коде в следующих строках при использовании этого шаблона:
try
{
service.SomeCall();
}
catch
{
// Игнорируется
}
Логирование как неперехваченных, так и перехваченных исключений — это меньшее, что вы можете сделать для своих пользователей. Нет ничего хуже, чем когда пользователи обращаются в вашу службу поддержки, и вы даже не подозреваете, какие были ошибки и что произошло. В этом вам поможет ведение логов.Существует несколько отличных фреймворков для ведения логов, таких как NLog и Serilog. Если вы веб-разработчик ASP.NET (Core), запись неперехваченных исключений может выполняться автоматически с помощью elmah.io или одного из других доступных инструментов.
Узнать подробнее о курсе "C# Developer. Professional".Смотреть вебинар «DI-контейнеры для C#».
===========
Источник:
habr.com
===========
===========
Автор оригинала: Thomas Ardal
===========Похожие новости:
- [JavaScript, Программирование, Клиентская оптимизация, Математика] Кэширование данных увеличивает скорость даже в неожиданных случаях
- [Интернет вещей] Умное фермерство. Как сельское хозяйство сокращает издержки с помощью IoT-решений?
- [Open source, Программирование, Dart, Flutter] Повышаем качество кода с Dart Code Metrics
- [Data Mining, Алгоритмы, Big Data, Машинное обучение] Рекомендации Друзей ВКонтакте: ML на эго-графах
- [Управление персоналом] Как нанять QA — вредные советы
- [Программирование, Разработка мобильных приложений, Учебный процесс в IT, Карьера в IT-индустрии] Апрельский дайджест: приглашаем на онлайн-практикумы и митапы
- [Обработка изображений, Машинное обучение, Компьютерное железо, DIY или Сделай сам] Edge платы для домашнего Computer Vision
- [Производство и разработка электроники, Компьютерное железо, Искусственный интеллект, Суперкомпьютеры, IT-компании] Nvidia анонсировала платформу для суперкомпьютеров, десктопный процессор на базе ARM и фреймворк для кибербезопасности
- [Кодобред, Робототехника, DIY или Сделай сам] Умельцы научили робота-собаку Spot мочиться пивом
- [Программирование, Rust] Как не копировать код в Rust
Теги для поиска: #_programmirovanie (Программирование), #_c#, #_csharp, #_windsor, #_autofac, #_di, #_obrabotka_iskljuchenij (обработка исключений), #_blog_kompanii_otus (
Блог компании OTUS
), #_programmirovanie (
Программирование
), #_c#
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:43
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В рамках скорого старта курса "C# Developer. Professional" подготовили для вас перевод материала.
Приглашаем также всех желающих на бесплатный демо-урок «DI-контейнеры для C#». На этом занятии мы: 1) Разберемся с тем, что такое принцип DI и зачем он нужен; 2) Научимся применять DI без использования контейнеров; 3) Рассмотрим два популярных DI-контейнеры для C#: Windsor и Autofac, разберем их плюсы и минусы; 4) Научимся регистрировать зависимости, управлять их жизненным циклом, применять инъекцию зависимостей. try
{ try { // Вызов какого-либо кода, который может сгенерировать исключение SpecificException } catch (SpecificException specificException) { log.LogError(specificException, "Specific error"); } // Вызов какого-либо кода } catch (Exception exception) { log.LogError(exception, "General erro"); } catch (SpecificException specificException)
{ // ... throw specificException; } catch (SpecificException specificException)
{ // ... throw; } var exception = new Exception("En error happened");
exception.Data.Add("user", Thread.CurrentPrincipal.Identity.Name); throw exception; try
{ service.SomeCall(); } catch (Exception e) { e.Data.Add("user", Thread.CurrentPrincipal.Identity.Name); throw; } try
{ File.WriteAllText(path, contents); } catch (Exception e) { logger.Error(e); } try
{ File.WriteAllText(path, contents); } catch (ArgumentException ae) { Message.Show("Invalid path"); } catch (DirectoryNotFoundException dnfe) { Message.Show("Directory not found"); } catch (Exception e) { var supportId = Guid.NewGuid(); e.Data.Add("Support id", supportId); logger.Error(e); Message.Show($"Please contact support with id: {supportId}"); } Address a = null;
var city = a.City; Address a = null;
var city = a?.City; var i = int.Parse("invalid");
if (int.TryParse("invalid", out int i))
{ } public class MyVerySpecializedException : Exception
{ public MyVerySpecializedException() : base() {} public MyVerySpecializedException(string message) : base(message) {} public MyVerySpecializedException(string message, Exception inner) : base(message, inner) {} public int Status { get; set; } } try
{ service.SomeCall(); } catch (MyVerySpecializedException e) when (e.Status == 500) { // Do something specific for Status 500 } catch (MyVerySpecializedException ex) { // Do something general } try
{ service.SomeCall(); } catch { // Игнорируется } Узнать подробнее о курсе "C# Developer. Professional".Смотреть вебинар «DI-контейнеры для C#».
=========== Источник: habr.com =========== =========== Автор оригинала: Thomas Ardal ===========Похожие новости:
Блог компании OTUS ), #_programmirovanie ( Программирование ), #_c# |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:43
Часовой пояс: UTC + 5