Cамоучитель по VB.NET

         

Инициирование исключений


Выше уже говорилось о том, что метод ProcessFilе просто передает исключение в процедуру Sub Main,

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

Лучше попытаться по возможности «прибрать» за собой, а затем воспользоваться ключевым словом Throw, чтобы передать объект исключения вызывающей стороне. В главе 4 упоминалось о том, что в VB .NET не поддерживается детерминированное завершение. Следовательно, если вы создали объект с методом D1 spose, этот метод следует вызвать перед тем, как инициировать исключение. Сказанное относится и к открытию файлов, и к получению графического контекста. В следующем фрагменте представлена условная структура подобного кода:

Try

' Создание локального объекта с методом Dispose

' Код. который может инициировать исключения

Catch(e As Exception)

local Object.dispose()

Throw e;

End Try

Если вы не вызовете метод Dispose для своего локального объекта, то захваченные ресурсы так и не будут освобождены. Ведь ссылка на объект существует лишь в локальном коде; остальные части программы не обладают доступом к методу Dispose! С другой стороны, причина, по которой возникло исключение, остается в силе, поэтому о возникшей проблеме (например, о неудачной операции с файлом) нужно сообщить вызывающему коду. Для этого следует заново инициировать исключение командой Throw, как это сделано во второй выделенной строке.

Впрочем, если вы действительно хотите программировать «как положено», не ограничивайтесь простым перезапуском исключения. Постарайтесь сделать свой код как можно более информативным и включите в объект исключения дополнительную информацию. Для этого есть три возможности.

  1. Добавьте в исключение содержательное сообщение и инициируйте его заново. Возможно, новая информация окажется полезной.
  2. Инициируйте исключение одного из стандартных типов, производных от типа текущего исключения, чтобы оно лучше описывало ситуацию.
  3. Создайте новый класс исключения, производный от типа текущего исключения, который будет описывать ситуацию лучше, чем любой из стандартных классов.
Решения расположены по возрастанию приоритета, и в идеальном случае следует всегда использовать пункт 3. На практике программисты при выборе руководствуются своей оценкой того, какую информацию об исключении необходимо передать для дальнейшей обработки.

Для примера представьте такую ситуацию: из источника данных читаются пары «ключ/значение», и для последнего ключа не находится парного значения. Программа предполагает, что значение ассоциируется с каждым ключом, поэтому при попытке чтения возникает неожиданно'е исключение ввода-вывода (чтение данных из файла описано в главе 9).

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

Public Sub New(ByVal message As String)

В следующем фрагменте в объект IOException добавляется новая строка с сообщением об отсутствии значения для последнего ключа, после чего исключение инициируется заново.

Dim excep As New IQException("Missing value for last key") Throw excep

Получив инициированное исключение, внешний код получает текст сообщения методом Message класса Exception и узнает о возникшей проблеме.


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

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

Public Class LastValueLostException Inherits System.I0.I0.Exception

Private mKey As String

Public Sub New(ByVal theKey As String)

MyBase.New("No value found for last key")

mKey = theKey

End Sub

Public Readonly Property LastKey() As String Get

Return mKey

End Get

End Property

End Class

Обратите внимание: имя созданного класса исключения завершается словом Exception. Это стандартное правило, которому мы настоятельно рекомендуем следовать. Получив исключение LastValueLostException, программист может воспользоваться свойством LastKey, значение которого передается в конструкторе нового класса исключения, и получить ключ, не ассоциируемый со значением. Следующая строка обеспечивает выдачу правильной информации методом Message базового класса Exception: MyBase.New("No value found for last key")

В этой строке вызывается конструктор базового класса (и в конечном счете конструктор предка Exception).

Возможно, вы заметили, что в классе LastValueLostException не переопределяются другие методы — такие, как метод ToString, унаследованный от Exception. В стандартных ситуациях объекты исключений всегда должны выводить стандартные сообщения.

Как использовать созданный класс в программе? Например, если последний ключ без парного значения был равен «oops», исключение будет инициироваться следующей командой:

Throw New LastValueLostException("oops")

 

Иерархия исключений

Мы создали новый класс исключений, производный от IOExcepti on, потому что потенциальная проблема явно относилась к категории ввода-вывода. Допустим, ситуация имеет более общий характер и для базового класса не существует других очевидных кандидатов, кроме класса Exception. Впрочем, это не совсем верно — лучший выбор существует всегда. Мы настоятельно рекомендуем выбирать в качестве базового не сам класс Exceptlon, а производный от него класс AppllcationException.

Дело в том, что .NET Framework различает исключения, возникшие в результате проблем исполнительной среды (например, нехватки памяти или дискового пространства) и проблем, обусловленных работой вашего приложения. Именно исключения второй категории должны быть производными от AppllcationExcepti on, поэтому именно этот класс следует выбирать базовым при определении обобщенных исключений в программе.


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

Рис. 7.1. Две основные ветви иерархии исключений

Классы Exceptlon, AppllcationExcepti on и SystemExcepti on обладают одинаковой функциональностью. Существование трех классов вместо одного — не более чем удобная абстракция, благодаря которой становится проще понять исключения, возникающие в ваших программах.

 

Исключения как замена для goto

Обработка исключений в сочетании с определением собственных классов исключений позволяет полностью отказаться от использования GoTo. Например, в главе 3 был приведен пример оправданного применения GoTo для прерывания вложенных циклов, когда ошибка происходит во внутреннем цикле. Программист VB .NET в подобной ситуации просто заключает весь цикл в блок Try-Catch, как показано ниже:

Sub Main()

Dim getData As String

Dim i, j As Integer

Dim e As System.I0.I0Exception

Try

For i = 1 To 10

For j = 1 To 100 Console.WriteC'Type the data, hit the Enter key between " & _

"ZZZ to end: ") getData _

Console.ReadLine() If getData = "ZZZ" Then

e New System.I0.I0Exception("Data entry ended " & _

"at user request") Throw e Else

' Обработка данных

End If

Next j

Next i

Catch

Console.WriteLinete.Message)

Console. Readline()

End Try

End Sub

В приведенном выше фрагменте выделенные строки нельзя объединить конструкцией следующего вида:

Dim e As New System.IO.IOException("Data entry ended at user request")

Вследствие правил видимости VB .NET объект исключения окажется недоступным в секции Catch.

 

Секция Finally

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

Чтобы некоторый фрагмент выполнялся независимо от того, возникнет ли в программе исключение или нет, в блок Try-Catch включается секция Finally, выделенная в следующем примере жирным шрифтом:

Sub Main()

Dim args(). argument As String

args = Environment. GetCommandLineArgs()

Try

ProcessFile(argsd))

Catch

Console.WriteLine("ERROR")

Finally

Console.WriteLine("Press enter to end")

Console.ReadLine()

End Try

End Sub


 

Рекомендации по использованию исключений

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

  1. Исключение является признаком аварийной ситуации; не используйте исключения для простой передачи информации (мы видели программу, в которой при успешном завершении функции инициировалось исключение SUCCESS_EXCEPTION).
  2. Не заменяйте тривиальные проверки обработкой исключений. Например, исключения не стоит применять для проверки достижения конца файла (EOF).
  3. Избегайте раздробленной обработки исключений, при которой едва ли не каждая команда заключается в отдельный блок Try-Catch. Заключение всей операции в один блок Try-Catch обычно предпочтительнее использования нескольких блоков.
  4. Не поглощайте исключения конструкциями вида Catch e As Excepti on с пустым блоком команд, если для этого нет достаточно веских причин. Такая конструкция эквивалентна бездумному применению On Error Resume в старых программах VB, и пользоваться ею нежелательно по тем же причинам. Если в программе произошло исключение, обработайте его или передайте для дальнейшей обработки.
  5. Последнюю рекомендацию скорее можно назвать «правилом хорошего тона». Передавая исключение во внешний код для последующей обработки, добавьте в него новую информацию (или определите новый класс исключений), чтобы внешний код мог точно определить, что произошло и какие меры были приняты для того, чтобы исправить ситуацию.


Содержание раздела