День шестьдесят четвёртый. #ЗаметкиНаПолях

Исключения. Продолжение

Советы по обработке исключений

1. Если блок catch определяет переменную исключения, ее можно использовать для получения дополнительных сведений о типе созданного исключения. Эту переменную обычно стоит рассматривать как доступную только для чтения, однако можно добавлять нужную информацию в коллекцию её свойства Data.

2. Программа может явным образом создавать исключения с помощью ключевого слова throw.

3. Объём блока кода внутри try зависит от управления состоянием. Если внутри блока вы собираетесь выполнить набор операций, каждая из которых может стать причиной исключения одного и того же типа, но при этом вы хотите обработать эти исключения по-разному, имеет смысл поместить операции в разные блоки try.

4. При отладке в Visual Studio для просмотра выброшенного исключения нужно добавить в окно Watch специальную переменную $exception.

5. Если обнаружится, что состояние приложения осталось испорченным даже после восстановительных операций в блоках catch или finally, имеет смысл его удалить, чтобы не создавать дополнительных проблем. Это делается методом AppDomain.Unload. После этого приложение перезагружается, чтобы состояние инициализировалось нормально. Если состояние кажется настолько плохим, что имеет смысл завершить работу приложения, используйте статический метод Environment.FailFast. Он завершает процесс без выполнения активных блоков try/finally и без вызовов метода Finalize.



Приёмы работы с исключениями:

1. Активно используйте блоки finally для очистки ресурсов.

- В C# блоки try/finally автоматически создаются компилятором для некоторых конструкций: в lock отключается блокировка, в using вызывается метод Dispose объекта, в foreach вызывается метод Dispose объекта IEnumerator, в деструкторах вызывается метод Finalize базового класса.

2. Не надо перехватывать все исключения.

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

3. Корректное восстановление после исключения.

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

4. Отмена незавершённых операций при невосстановимых исключениях.

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

5. Скрытие деталей реализации для сохранения контракта

- Иногда бывает полезно после перехвата одного исключения, выбросить исключение другого типа для сохранения деталей реализации. Например, создать исключение RecordNotFoundException для операций поиска в файле или базе данных и выбрасывать его вместо исключений типа FileNotFound, IOException или исключений, связанных с доступом к базе данных.

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



Источники:

- Джеффри Рихтер “CLR via C#”. 3-е изд. – СПб.: Питер, 2012. Глава 20.

-
https://docs.microsoft.com/ru-ru/dotnet/csharp/programming-guide/exceptions/