Отладка и оптимизация программ. Отладка
Оператор Resume
Оператор Resume должен быть последним выполняемым оператором обработчика ошибок. Он определяет точку процедуры, которой передается управление по завершении обработки ошибки. У него также три варианта синтаксиса:
Resume [0] Resume Next Resume строка
В первом случае выполнение программы продолжается с повторного выполнения оператора, вызвавшего ошибку. Заметим, если это был оператор, инициировавший вызов процедур и функций, приведших в конечном итоге к ошибке, то управление передается этому оператору. Другими словами, управление всегда передается оператору охраняемого блока, явно или неявно инициировавшему ошибку. Вариант Resume (или Resume 0 ) естественно использовать, когда ошибка вызвана вводом неверных данных пользователем, а в обработчике ошибок у него запрашиваются новые правильные данные. Например, если в основной процедуре пользователь ввел имя несуществующего файла, в обработчике можно запросить новое имя и продолжить выполнение с повторения оператора открытия файла. Таким образом, этот вариант используется, если в обработчике ошибок устранена причина ошибки. В этом случае можно надеяться, что повторное исполнение оператора, ранее инициировавшего ошибку, теперь завершится благополучно.
В варианте Resume Next после обработки ошибки выполнение процедуры продолжается с оператора, следующего за оператором, инициировавшим ошибку. Не будем повторяться, и здесь речь идет об операторах охраняемого блока, а не тех вызванных процедур, где реально произошла ошибка. Если в первом варианте обработчик ошибки должен устранить причину ошибки, то в этом варианте обработчик устраняет последствия ошибки, выполняя, по существу, работу оператора, приведшего к ошибке. Еще одна возможная ситуация применения этого варианта, о которой мы уже упоминали, состоит в том, что следующий оператор сам анализирует причину ошибки и задает способы ее устранения.
В третьем варианте параметр строка задает метку строки или номер строки данной процедуры, на которую передается управление после обработки ошибки. Этот вариант используется в разных ситуациях, например, причина ошибки устранена, но необходимо повторить часть вычислений, предшествовавших появлению ошибки. Возможно, также, что устранены последствия ошибки, но действия надо продолжить не со следующего оператора, а с определенного участка процедуры. Третий вариант задает общую конструкцию, и мог бы использоваться во всех трех случаях.
Объект Err
Объект Err содержит информацию о последней ошибке выполнения. Объект Err является внутренним объектом с глобальной областью определения. Нет необходимости в создании этого объекта. Он создается вместе с проектом. Вот список свойств объекта Err и их значений:
Рассмотрим пример, в котором возникает ошибка периода выполнения. Обработчик ошибки выдает сообщение о ней, используя свойства объекта Err. Затем в обработчике устраняется причина возникновения ошибки и управление возвращается оператору, инициировавшему запуск процедуры, приведшей к ошибке. Вся эта ситуация демонстрируется на примере работы с уже известной функцией fact2, вычисляющей корректно значение факториала для ограниченного диапазона значений входного параметра.
Public Function Fact2(ByVal N As Integer) As Integer 'Функция спроектирована для вычисления факториалов чисел, не больших 7 #If conDebug Then Debug.Assert (N >= 0) And (N < 8) #End If If (N = 0) Or (N = 1) Then ' базис индукции. Fact2 = 1 ' 0! =1. Else ' рекурсивный вызов в случае N > 0. Fact2 = Fact2(N - 1) * N End If #If conDebug Then Debug.Assert Fact2 <= 5040 #End If End Function
Заметьте, поскольку флаг отладки (conDebug) уже отключен, то Assert - утверждения не работают. Приведем процедуру, вызывающую функцию fact2 первый раз корректно, второй - нет, что приведет к ошибке, ее перехвату и исправлению ситуации:
Public Sub TestFact2() Dim Msg As String Dim VictoryCount As Integer, Prize As Long On Error GoTo ErrHandler1 VictoryCount = 5 Prize = Fact2(VictoryCount) * 5 Debug.Print VictoryCount, Prize VictoryCount = 10 Prize = Fact2(VictoryCount) * 5 Debug.Print VictoryCount, Prize Exit Sub ErrHandler1: Msg = "Ошибка # " & Err.Number & " возникла в " & Err.Source _ & vbCrLf & " Описание: " & Err.Description _ & vbCrLf & " HelpFile: " & Err.HelpFile _ & vbCrLf & " HelpContext: " & Err.HelpContext MsgBox Msg, vbMsgBoxHelpButton, "Error", Err.HelpFile, Err.HelpContext 'Грубое устранение причин ошибки Err.Clear If VictoryCount < 0 Then VictoryCount = 0 If VictoryCount > 7 Then VictoryCount = 7 Resume End Sub10.3.
Вот как выглядит окно сообщения, выведенное в обработчике ошибки.
Заметьте, после выдачи сообщения процедура нормально завершает свою работу и в окне проверки Immediate появятся следующие результаты:
5 600 7 25200
Объект Err специально спроектирован для работы на этапе обнаружения и исправления ошибок периода выполнения. Он заменил ранее существовавшие функцию и оператор Err. Для совместимости с ними свойство Number реализовано, как свойство по умолчанию и его можно не указывать. Если в борьбе с ошибками на этапе отладки важную роль играет объект Debug, то не менее важна роль объекта Err при борьбе с ошибками периода выполнения. Также как и объект Debug, объект Err имеет всего два метода - Clear и Raise. Рассмотрим их подробнее.
Метод Clear
Его синтаксис:
Err.Clear
Этот метод используется для явной очистки значений свойств объекта Err после завершения обработки ошибки. Автоматическая очистка свойств Err происходит также при выполнении операторов:
- оператора Resume любого вида;
- Exit Sub, Exit Function, Exit Property ;
- оператора On Error любого вида.
Метод Raise
Метод Raise генерирует ошибку выполнения. У него двоякое назначение. Во-первых, он используется для моделирования стандартных, внутренних ошибок. Необходимость в этом возникает, например, при отладке обработчиков соответствующих ошибок. Но его главное назначение состоит в возбуждении собственных ошибок, когда в результате проделанного анализа обнаружена исключительная ситуация. Его синтаксис:
Err.Raise number, source, description, helpfile, helpcontext
Параметры метода имеют тот же смысл, что и соответствующие свойства объекта Err. Обязателен лишь параметр Number. При моделировании внутренних ошибок только этот параметр указывается при вызове метода. При возбуждении собственных ошибок разумно задавать все параметры, для того, чтобы были определены свойства объекта Err.
Параметр Number имеет тип Long и определяет код ошибки. Коды ошибок (как внутренних, так и определяемых пользователем) лежат в диапазоне 0-65535. При этом коды от 0 до 512 зарезервированы за системными ошибками VBA. При возбуждении собственных ошибок при задании параметра Number к ее собственному коду необходимо добавлять константу vbObjectError + 512, что позволяет системе отличать внутренние и пользовательские ошибки.
Перед вызовом метода Raise для возбуждения собственной ошибки полезно предварительно очистить объект Err методом Clear. Если Вы не зададите некоторые параметры в вызове Raise, в качестве их значений используются текущие значения соответствующих свойств объекта Err.
Класс и обработка ошибок
Мы уже говорили, что важная часть работы программиста по отладке программы состоит в том, чтобы предохранить работающую программу от прерывания ее работы при возникновении внутренних ошибок. Не менее важно, особенно при работе с объектами пользовательских классов, предусмотреть возможность появления исключительных ситуаций, генерировать и соответственно обрабатывать собственные ошибки класса. В качестве примера приведем класс Day, у которого есть два свойства - дата и температура на эту дату. Методы класса, (их реализацию мы не приводим) исходят из того, что летом должно быть жарко, а зимой - холодно. Нарушение этого условия приведет к неверным результатам. Поэтому в состав класса включен метод CheckDay, проверяющий корректность задания свойств. В случае несоблюдения требуемых условий метод генерирует собственные ошибки класса. Вот описание нашего класса:
Option Explicit 'Класс Day 'Свойства класса Private today As Date Private temperature As Integer Public Property Get Сегодня() As Date Сегодня = today End Property Public Property Let Сегодня(ByVal NewValue As Date) today = NewValue End Property Public Property Get Температура() As Integer Температура = temperature End Property Public Property Let Температура(ByVal NewValue As Integer) temperature = NewValue End Property Public Sub CheckDay() Dim Desc As String Dim Numb As Long Dim Source As String 'Проверка свойств объекта Select Case Month(Сегодня) Case 6 To 8 If Температура < 0 Then 'Исключительная ситуация Desc = "Ошибка: Работа с объектом предполагает положительную летнюю температуру!" Numb = vbObjectError + 513 Source = " Метод CheckDay класса Day " Err.Raise Numb, Source, Desc End If Case 1 To 2, 12 If Температура > 0 Then 'Исключительная ситуация Desc = "Ошибка: Работа с объектом предполагает отрицательную зимнюю температуру!" Numb = vbObjectError + 514 Source = " Метод CheckDay класса Day " Err.Raise Numb, Source, Desc End If End Select End Sub10.4.
Приведем теперь процедуру, работающую с объектами, этого класса:
Public Sub WorkWithDay() 'Работа с объектами класса Day Dim myday As New Day Dim Msg As String 'Охраняемый блок On Error GoTo ErrorHandler myday.Сегодня = "9.8.99" myday.Температура = -15 myday.CheckDay Debug.Print myday.Сегодня, myday.Температура Exit Sub ErrorHandler: If Err.Number = vbObjectError + 513 Then Msg = vbCrLf & "Введите температуру сегодняшнего дня " _ & myday.Сегодня & vbCrLf & " Учтите, она должна быть положительной" myday.Температура = InputBox(Err.Source & vbCrLf & Err.Description & Msg, "CheckDay", 15) ElseIf Err.Number = vbObjectError + 514 Then Msg = vbCrLf & "Введите температуру сегодняшнего дня " _ & myday.Сегодня & vbCrLf & " Учтите, она должна быть отрицательной" myday.Температура = InputBox(Err.Source & vbCrLf & Err.Description & Msg, "CheckDay", -15) End If Resume End Sub10.5.
Заметьте, эта процедура, работающая с объектом myday класса Day, построена по всем правилам, - в ней есть охраняемый блок. При возникновении ошибки, а она действительно возникает из-за некорректного задания свойств объекта, производится захват ошибки, управление передается предусмотренному обработчику ошибки. В обработчике пользователю разъясняется суть ситуации, приведшей к ошибке, после чего он вводит корректные данные. Взгляните, как выглядит окно для диалога с пользователем на этом этапе:
После того, как пользователь задал нормальную летнюю температуру, процедура нормально завершила свою работу. В окне проверки напечатаны следующие результаты:
09.08.99 25