Поискав по рунету материал на тему обработки ошибок в VBA, не увидал на первых двух страницах результатов поиска чего-то, что мне понравилось. Может плохо смотрел, но решил написать на эту тему свою статью.
Простите, но — немного словоблудия
Ошибки в программе
Ошибки времени исполнения программы возникают, когда среда программирования не может выполнить то, что вы хотите. Таких ситуаций может быть много. Например:
-
Вы обращаетесь к объекту по имени, а объекта с таким именем в коллекции нет
-
Вы хотите выделить ячеку на одном листе, а этот лист в данный момент не является активным (типичнейшая ошибка новичков в Excel VBA)
-
Вы хотите удалить отфильтрованные автофильтром строки, а фильтр вообще не вернул записей и удалять нечего
-
Вы ссылаетесь на элемент массива, который находится за пределами его границ.
-
Вы пытаетесь присвоить переменной значение, которое оно не может хранить. Например, переменной типа Long нельзя присвоить строковую константу или переменной типа Integer присвоить знанчение превышающее число 32767.
На любую из этих и сотни других ситуаций среда выполнения реагирует стандартно — прерывает ход выполнения программы на том операторе, где возникла ошибка или, как ещё принято говорить, исключение. На экран выводится информация о возникшей ошибке и предлагаются стандартные варианты для продолжения работы:
-
Continue (продолжить) — этот пункт во время возникновения ошибки всегда не активен. Он активен, когда по ходу выполнения программы вы использовали оператор Stop. Кстати это очень полезный оператор для отладки программы.
-
End (завершить) — завершение исполнения программы
-
Debug (отладка) — переход в режим отладки, в котором можно посмотреть, на каком операторе возникла ошибка, что содержат переменные, можно даже перетащить жёлтую полоску, подсвечивающую текущий оператор, назад, и модифицировать знанчение переменных через окно Immediate window (впрочем это экзотика). В общем случае кнопка Debug позволяет посмотреть, где случилась ошибка и попытаться понять почему так случилось.
Если вы — автор программы, в которой случилась ошибка, то вы, должно быть, в начале будете рады увидеть подобное окно, ибо только так вы сможете отловить основные ошибки, скрытые в вашем коде. Однако, если эту ошибку видит пользователь, то для него это, мягко говоря, безрадостное и малопонятное зрелище. Ещё хуже, если за эту программу вам заплатили деньги. Поэтому в среде худо-бедно профессиональных программистов принято предусматривать обработку ошибок в своих программах.
Почему вообще в коде возникают ошибки?
-
Много ошибок во время написания кода возникает по невнимательности или не совсем адекватного понимания того, что делаешь. Таких ошибок, как правило, очень много, особенно у начинающих программистов, но эти ошибки довольно легко отловить и исправить, так как, пока вы их не исправите, ничего не работает. Ну, например, вы должны извлечь данные из 5-го столбца, а вы извлекаете из 6-го, а их там банально нет. Ясно, что вы это очень быстро заметите.
-
Вторая группа ошибок — это ошибки оптимиста. Когда программа написана в целом правильно, но алгоритм не готов к ударам судьбы в виде неожиданных действий со стороны пользователя, ошибок ввода-вывода (вы рассчитывали считать данные из файла, а файла с таким именем не оказалось, либо он заблокирован другим приложением), особенностей конфигурации компьютера (разные версии ОС или офиса, которые в некоторых мелочах отличаются).
-
Тонкие логические ошибки. Чем сложнее программа, тем больше шансов, что модель задачи в вашей голове, ваша программа и реальность не совсем согласованы между собой. Пока вы не достигните достаточного погружения в задачу вы такие ошибки не найдёте и не исправите. Порой на это уходит много времени. Но это характерно для сложных задач.
-
Ошибки на стыке вашего приложения и сервисов ОС, приводящие к неожиданным крахам приложения. Такого вообще возникать не должно, но как мы понимаем, и ОС и офис содержат ошибки, да и вы (что более вероятно) можете пользоваться системными вызовами не правильно. Подобные ошибки — сущий кошмар, особенно когда они проявляются лишь на некоторых конфигурациях, при определенных условиях, их трудно поймать и надёжно воспроизвести.
Задачи механизмов обработки ошибок
-
Обеспечить стабильную работу программы. Возникновение ошибки, появление которой вы не предусмотрели, приведёт в большинстве случаев к аварийному завершению всей программы или её части. При определенном уровне подобных ситуаций это ведёт к тому, что программой пользоваться становится невозможно.
-
Информирование. Мало обработать ошибку и предотвратить завершение программы. Надо ещё и адекватно проинформировать пользователя о причинах нестандартного поведения программы. Частно причиной ошибок в программе являются некорректные действия пользователя, поэтому важно сообщать ему о них.
-
Защита данных от повреждения. Программа обязана защищать от непреднамеренных повреждений результаты своей или пользовательской работы. Деструктивные действия должны быть снабжены соответствующими предупредительными диалоговыми окнами. Часто ошибка, не обработанная должным образом может повредить нужные данные.
Файл примера
Скачать
Код без обработки ошибок
Вот простой пример с потолка. Если вызвать Example_00, то она прекрасно отработает без ошибок и вернёт это:
В функцию GetCalories передаётся строка с блюдом, а она должна вернуть его калорийность, сверившись с таблицей в A1:B7.
Давайте поищем слабые места в этом коде. Первое, что должно прийти в голову — если мы ищем, то, что произойдёт, если мы не найдём? А произойдёт, конечно же, ошибка. Её инициирует метод Match.
Ещё одно слабое место этой подпрограммы: функция возвращает вещественный тип Double, и даже, если поиск оказался удачным, то в Cells(intRow, 2) может случайно находиться текстовая строка, а потому, когда вы числовому типу попытаетесь присвоить строковый тип, также произойдёт ошибка. И, если вы второй ошибки сможете избежать за счёт дополнительного оператора if с проверкой через IsNumber(), то избежать первой ошибки таким способом нельзя. Что же делать? А вот тут на сцену выходят операторы обработки ошибок.
Есть 2 подхода к обработке ошибок: автономный подход и выносной. Эти термины я придумал только что, чтобы проще было их обсуждать.
Автономный подход
Смысл автономного подхода в том, чтобы не выносить сор из избы. Если в подпрограмме возникла ошибка, то мы должны предположить, на каком месте она возникнет и поджидать её там с дубиной. С ошибкой, в этом случае, разбираются обычно в операторе, идущем сразу после потенциально опасного места. Давайте смотреть, как это может выглядеть:
Итак, что тут сделано:
-
Сразу после объявления функции GetCalories_v1 идёт оператор on error resume next, который в случае возникновения в каком-либо месте ошибки, предписывает VBA просто передавать управление на следующий оператор, идущий после ошибочного.
-
Мы объявили переменные. Необъявленные переменные получают тип Variant и значение по умолчанию Empty. Объявленные переменные числовых типов инициируются нулём, строковые — пустой строкой, то есть я наперёд знаю, что они содержат, а это хорошо для обработки ошибок.
-
На вызове метода WorksheetFunction.Match у нас возникает ошибка, так как искомого значения в таблице нет. А это, между прочим, был оператор присваивания ( = ). Прежде, чем левой части оператора присваивания (intRow) что-то будет присвоено, необходимо вычислить правую часть оператора присваивания (WorksheetFunction.Match…), а поскольку в процессе этого вычисления возникает ошибка, то переменная intRow остаётся такой, какой была! А, как я уже сказал, VBA автоматически её инициализирует нулём до начала исполнения подпрограммы. Получается, что, если в этом операторе возникнет ошибка, то в intRow будет ноль. Если ошибки во время поиска не возникнет, то ноля там не будет ни при каких раскладах, так как строки на листе нумеруются с единицы.
-
И вот этот ноль мы и контролируем, добавляя оператор If. Если intRow больше нуля, то WorksheetFunction.Match отработала штатно, а если нет — то работу подпрограммы надо прерывать, но об этом чуть позже.
-
Далее мы помним, что Cells(intRow, 2) может теоретически вернуть строковое значение, которое вызовет ошибку Type missmatch при присвоении переменной типа Double (GetCalories_v1), поэтому мы вставляем дополнительную проверку промежуточной переменной varTemp тому, что она числовая. И если это так, то присваиваем GetCalories_v1 значение из varTemp.
-
В случае возникновения любой ошибки внутри GetCalories_v1 она просто вернёт ноль. Почему ноль? Потому что переменная GetCalories_v1 тоже инициализируется нулём и об этом не надо заботиться, а в случае ошибки она останется в неприкосновенности.
-
Соответственно родительский код (в нашем случае его роль играет процедура Example_01) должен проверить, а не вернёт ли GetCalories_v1 ноль, и быть готовым к этой ситуации.
-
А вот теперь тонкий момент, который не все понимают. Почему я использовал промежуточные переменные intRow и varTemp? Вроде бы есть очевидный ответ — чтобы не вычислять значение выражений с Match и Cells 2 раза. Отчасти это, конечно, так. Но это, в данном случае, не главная причина. Главная причина в том, что такой код
вызовет неправильное поведение программы. Если у нас Match вызовет исключение, то VBA передаст управление на СЛЕДУЮЩИЙ оператор, а следующий оператор в данном случае это то, что идёт после Then — присваивание переменной varTemp значения. Таким образом наша проверка на наличие ошибки сработает с точностью до наоборот, передав управление в ту часть кода, которая должна быть защищена от ситуации, когда Match не нашла строку в таблице. Вот почему важно в операторе If не иметь ничего такого, что могло бы вызвать ошибку.
-
Как видите, в этом подходе мне зачастую даже нет необходимости проверять объект Err, чтобы понять, что произошла ошибка, так как я ориентируюсь на то, что промежуточные переменные остаются неинициализированными, что является показателем наличия ошибки.
Выносной подход
Данный метод основан на том, что, когда возникает ошибка, то VBA передаёт управление на специальный участок кода — обработчик ошибок, который обычно размещают в конце подпрограммы. Это может выглядеть так:
Обратите внимание, что:
-
Оператор on error теперь в случае ошибки предписывает передавать управление на метку ErrorHandler, которая объявлена в конце кода процедуры GetCalories_v2
-
В коде мы никак не заботимся о каких-либо проверках. Возникла ошибка? Иди на метку — там разберутся.
-
Если ошибки не случилось, то, чтобы программа не стала исполнять строчки, предназначенные для обработки ошибок, перед меткой ErrorHandler обычно ставят оператор Exit Sub или Exit Function (в зависимости от типа подпрограммы).
-
Принципиальный момент — наличие оператора On Error Resume Next сразу после метки ErrorHandler. Дело в том, что после того, как вы перешли на метку ErrorHandler, очень опасно иметь действующим оператор On Error GoTo ErrorHandler, так как, если у вас в обработчике ошибки случится любая ошибка, то управление будет передано опять на метку и, как нетрудно понять, образуется бесконечный цикл. Поэтому сразу после метки мы возможность возникновения цикла ликвидируем оператором On Error Resume Next.
Что лучше?
Какой метод лучше применять зависит от ваших предпочтений и конкретных ситуаций. Грамотную обработку ошибок можно сделать и так и эдак. Вот несколько соображений по преимуществам и недостакам данных подходов:
Автономный подход
Преимущества | Недостатки |
Есть возможность точно идентифицировать каждую конкретную проблему (если вы её предусмотрели), возникающую во время исполнения, что позволит вам дать самые точные инстркции пользователю для предотвращения появления исключения в будущем. | Достаточно трудоёмок, так как подразумевает наличие большого количества проверок в коде. Каждое потенциально опасное действие должно быть снабжено соответствующим оператором If, в котором контролируется значение переменной или код ошибки. |
Надо хорошо представлять себе ситуации, где могут возникнуть ошибки, в противном случае ряд ошибок вы просто не заметите на этапе отладки. | |
Необходимо больше кода, а также требуется опыт и фантазия. | |
Необходимо больше промежуточных переменных |
Выносной подход
Преимущества | Недостатки |
Ни одна ошибка не проскочит незамеченной. | Не смотря на то, что вы перехватите все ошибки, отреагировать на них правильно затруднительно, так как вы, по большому счёту, не знаете, на каком операторе произошла ошибка и почему. |
Удобнее организовывать централизованный сбор логов по ошибкам в приложении. Однозначно, фаворит для больших проектов. |
Кратко пробежимся по операторам, функциям и объектам VBA, которые предназначены для обработки ошибок времени исполнения программы.
Операторы
On Error { GoTo label | Resume Next | GoTo 0 }
Оператор on error управляет тем, на какой участок вашего кода будет передано управление в случае возникновения ошибки. Данный оператор можно вставить в любое место вашей программы или подпрограммы. Есть 3 варианта:
-
On error goto label — после того, как этот оператор выполнен, ошибка, возникшая на других операторах программы приведёт к переходу на метку label.
-
On error resume next — после такого оператора, VBA будет игнорировать возникшую ошибку и передавать управление на следующий оператор, стоящий за тем, в котором возникла ошибка.
-
On error goto 0 — это режим по-умолчанию. В случае возникновения ошибки данный режим приведёт к появлению на экране стандартного обработчик ошибок VBA с кнопками End и Debug.
Resume { label | Next | [0] }
Данный оператор возобновляет выполнение программы. Применяется в выносном методе обработки ошибок.
-
resume label— возобновление с метки label
-
resume next — возобновление со следующего оператора
-
resume или resume 0 — возобновление с оператора, вызвавшего ошибку. Это имеет смысл, если вы устранили ошибку в своём обработчике. На мой взгляд, на практике такое применяется крайне редко.
Goto label
Переход на метку. Может пригодиться, однако, использование меток в коде для чего-то большего, чем обработка ошибок, считается страшным моветоном.
Exit { Do | For | Function | Sub }
Досрочный выход из циклов (Do или For) и досрочный выход из подпрограмм (функции или процедуры). Могут пригодиться при обработке ошибок, но вообще это операторы и без того чрезвычайно полезны.
Объект Err
-
Err — глобальный объект (его не надо объявлять, а можно сразу пользоваться), который содержит информацию о последней ошибке, случившейся в вашей программе. Проверяя Err сразу после возникновения исключения или после ситуации, которая могла привести к исключению, вы можете понять, что имело место на самом деле.
-
Свойство Err.Number — содержит числовой код ошибки, по которому их различают в программе. Поскольку Number — свойство по умолчанию, то вы можете его опускать, то есть Err и Err.Number — это эквиваленты. Значение ноль говорит о том, что ошибки не произошло.
-
Err.Description — содержит англоязычное краткое описание ошибки
-
Err.Source — возвращает имя модуля, в котором возникла ошибка
-
Err.Clear — сбрасывает последнюю ошибку. Err сбрасывается также при выполнении оператором Resume, Exit (любого типа кроме Do и For) и On Error.
-
Err.Raise — искусственно вызывает исключение указанного в переданном параметре типа. Можно использовать для тестирования вашей подсистемы обработки ошибок.
P.S.
Лично я привык в своих программах использовать автономный подход и, возможно, поэтому я не совсем осознаю все преимущества выносного подхода. Буду рад прочесть в комментариях ваше мнение на этот счёт. Тема обработки ошибок данной статьёй, конечно, быть исчерпана не может, но она послужит вам хорошей стартовой точкой в этом важном деле.
Читайте также:
-
Работа с объектом Range
-
Работа с объектом Range (часть 2)
-
Sheet happens
-
Поиск границ текущей области
-
Массивы в VBA
-
Структуры данных и их эффективность
-
Автоматическое скрытие/показ столбцов и строк
Search code, repositories, users, issues, pull requests…
Provide feedback
Saved searches
Use saved searches to filter your results more quickly
Sign up
В языке VBA обработка
ошибок сосредоточена на уровне процедуры
(функции). В каждой процедуре может быть
выделен один или несколько охраняемых
блоков, с каждым из которых связывается
свой обработчик ошибки. Если во время
работы охраняемого блока возникла
ошибка (исключение), то нормальный ход
выполнения процедуры приостанавливается,
управление ее работой перехватывается
и передается обработчику ошибки.
Стандартный объект Err
содержит информацию об ошибке. Поэтому
в обработчике ошибки имеется возможность
обработать возникшую ситуацию, исправить
ее, запросив, например, у пользователя
дополнительные данные, и принять
правильное решение о дальнейшем ходе
выполнения программы. В некоторых
случаях, когда устранена причина ошибки
или ее последствия, управление может
быть возвращено в охраняемый блок, так
что вычисления будут продолжены. В
некоторых случаях работа программы
приостанавливается с выдачей пользователю
вразумительного объяснения причин,
приведших к невозможности дальнейшего
выполнения программы.
Давайте разберемся,
что значит «возникла ошибка»? Точнее
следует говорить возбуждена (raise) ошибка.
Кто обнаруживает ошибку? Обнаружение
исключительной ситуации и возбуждение
ошибки может быть сделано самой
операционной системой (VBA) или исполняемой
процедурой. Ошибки, возбуждаемые
операционной системой, могут быть
следствием аппаратных прерываний,
например, из-за деления на ноль, вычисления
корня из отрицательного числа, но это
могут быть и ошибки, программно
обнаруживаемые операционной системой,
например, при попытке открыть несуществующий
файл. Все эти ошибки будем называть
системными или внутренними ошибками
VBA. Все они тщательно классифицированы
и каждая из них однозначно идентифицируется
своим номером. Другую группу ошибок
составляют собственные или пользовательские
ошибки, возбуждение которых предусматривает
программист. Например, при работе с
объектом пользовательского класса
программист может и должен предусмотреть
специальную процедуру Check,
которая проверяет правильность задания
свойств объекта. Если обнаруживается,
что свойства объекта заданы некорректно,
так что выполнение операций над ним
приведет к неверным результатам, то
возбуждается собственная ошибка.
Конечно, также как и для стандартных
ошибок, ее тип должен быть полностью
определен, задан ее номер и другие
параметры. Возможно, программное
обнаружение исключительных ситуаций
и возбуждение собственных ошибок это
наиболее важная и наиболее трудная
часть программистской работы по
управлению ошибками. Заметим, что какие
бы ошибки не возбуждались, — внутренние
или пользовательские, в момент возбуждения
ошибки заполняются свойства объекта
Err,
так что он содержит всю информацию о
последней возникшей ошибке.
Синтаксически
охраняемый блок окружен специальными
операторами On
Error. В начале
блока оператор On
Error задает
метку обработчика ошибки охраняемого
блока. Обработчик ошибок, как правило,
завершается специальным оператором
Resume,
который задает точку в процедуре, которой
передается управление после завершения
обработки ошибки. Приведем схему
процедуры с тремя охраняемыми блоками:
Sub
ProcWithErrors()
‘Первый
охраняемый блок
On
Error GoTo ErrHadler1 ‘ подключение 1-го обработчика
ошибок
‘
Первая часть процедуры, которая может
вызвать ошибку.
…
On
Error GoTo 0 отключение 1-го обработчика
ошибок
‘Второй
охраняемый блок
On
Error GoTo ErrHadler2 ‘ подключение 2-го обработчика
ошибок
‘
Вторая часть процедуры, которая может
вызвать ошибку.
…
On
Error GoTo 0 отключение 2-го обработчика
ошибок
‘Третий
охраняемый блок
On
Error GoTo ErrHadler3 ‘ подключение 3-го обработчика
ошибок
‘
Третья часть процедуры, которая может
вызвать ошибку.
…
On
Error GoTo 0 отключение 3-го обработчика
ошибок
RepeatPoint: ‘
точка, с которой возобновляется выполнение
‘после
обработки ошибки в 3-ей части
…
Exit
Sub ‘выход из процедуры при отсутствии
ошибок
‘ОбработкаОшибок:
ErrHandler1:
‘
1-ый обработчик ошибок
…
Resume ‘возврат
к оператору, вызвавшему ошибку в 1-ой
части
ErrHandler2:
‘
2-ой обработчик ошибок
…
Resume
Next ‘переход к оператору, следующему за
оператором
‘вызвавшим
ошибку во 2-ой части
ErrHandler3:
‘
3-ий обработчик ошибок
…
Resume
RepeatPoint ‘переход к строке, с которой
возобновляется
‘выполнение
после обработки ошибки в 3-ей части
End
Sub
Пример
10.2. (html,
txt)
Такова общая,
достаточно простая схема обработки
ошибок (исключений) в языке VBA. Стоит
обратить внимание на то, что ситуация
все же не столь проста, как может
показаться с первого взгляда. Дело в
том, что любой охраняемый блок может
содержать вызовы процедуры процедур и
функций. Поэтому реальная ситуация
обычно такова, — один из операторов
охраняемого блока запускает цепочку
вызовов процедур и функций, каждая из
которых имеет свои охраняемые блоки и
свои обработчики ошибок. Ошибка может
произойти на каком-то шаге в одной из
вызванных процедур. Какие обработчики
будут вызываться и в каком порядке, об
этом поговорим чуть позже. Чтобы
разобраться с деталями, вначале стоит
подробно рассмотреть возможности
используемых средств — операторов On
Error, Resume
и объекта Err
с его свойствами и методами.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #
- #
Ошибки периода выполнения и их обработка
Итак, отладка программы завершена, последняя найденная ошибка исправлена. Теперь программа должна быть передана пользователю. Значит ли это, что в ходе работы пользователя с программой не будут возникать ошибки? Обязательно, будут! Нужно предпринять специальные меры, чтобы появление этих ошибок не приводило к неприятным последствиям. Если этого не сделать, то при возникновении ошибки на экране появляется сообщение, как правило, мало что говорящее пользователю и программа завершает свою работу. Как правило, все это сопровождается нелестными высказываниями пользователя в адрес разработчика. Заметьте, ошибки этого периода (run time errors) могут быть и в правильно работающей программе. Они могут возникать из-за неверных действий самого пользователя, не знающего спецификаций. Обычно ошибки связаны с вводом данных несоответствующих типов или вводом значений, выходящих за пределы допустимого диапазона. Пользователь может пытаться открыть несуществующий файл, или файл, который он необдуманно удалил. При работе
пользователя, например, в Access может быть сделана попытка открытия несуществующей таблицы или формы. В общем, у пользователя есть масса возможностей нарушить спецификации, особенно, если они не четко сформулированы. Но не стоит обольщаться, многие ошибки на совести программиста. Правильно считать, что во всех случаях виноват программист. В его задачу входит обнаружение и обработка всех исключительных ситуаций, возникающих в процессе работы программы. Сейчас мы и переходим к рассмотрению самого понятия исключительной ситуации и о тех средствах, которые есть в VBA для их обработки.
Исключительная ситуация или исключение (exception) возникает при выполнении программы и делает ее дальнейшее выполнение невозможным или нецелесообразным ввиду неопределенности, непредсказуемости или неправильности дальнейшего результата вычислений. Управление исключениями (exception handling) включает специально предусмотренное обнаружение исключений, перехват управления выполнением программы при возникновении исключения и передачу этого управления специальному разделу программы — обработчику исключения.
Определения, которые мы дали, носят общий характер и применимы к любому языку программирования. Следует сказать, что стандарт на исключения, их классификацию, способы обработки, еще не сформировался. Например, в языке Visual C++ обработка исключений значительно изощренней, чем в языке VBA. Следует сказать, что в VBA, к сожалению, не используется общепринятый термин исключение, вместо него используется термин ошибка. Суть дела от этого не меняется. Давайте начнем с рассмотрения общей схемы управления ошибками (исключениями) в языке VBA.
Модель управления ошибками в языке VBA.
В языке VBA обработка ошибок сосредоточена на уровне процедуры (функции). В каждой процедуре может быть выделен один или несколько охраняемых блоков, с каждым из которых связывается свой обработчик ошибки. Если во время работы охраняемого блока возникла ошибка (исключение), то нормальный ход выполнения процедуры приостанавливается, управление ее работой перехватывается и передается обработчику ошибки. Стандартный объект Err содержит информацию об ошибке. Поэтому в обработчике ошибки имеется возможность обработать возникшую ситуацию, исправить ее, запросив, например, у пользователя дополнительные данные, и принять правильное решение о дальнейшем ходе выполнения программы. В некоторых случаях, когда устранена причина ошибки или ее последствия, управление может быть возвращено в охраняемый блок, так что вычисления будут продолжены. В некоторых случаях работа программы приостанавливается с выдачей пользователю вразумительного объяснения причин, приведших к невозможности дальнейшего выполнения программы.
Давайте разберемся, что значит «возникла ошибка»? Точнее следует говорить возбуждена (raise) ошибка. Кто обнаруживает ошибку? Обнаружение исключительной ситуации и возбуждение ошибки может быть сделано самой операционной системой (VBA) или исполняемой процедурой. Ошибки, возбуждаемые операционной системой, могут быть следствием аппаратных прерываний, например, из-за деления на ноль, вычисления корня из отрицательного числа, но это могут быть и ошибки, программно обнаруживаемые операционной системой, например, при попытке открыть несуществующий файл. Все эти ошибки будем называть системными или внутренними ошибками VBA. Все они тщательно классифицированы и каждая из них однозначно идентифицируется своим номером. Другую группу ошибок составляют собственные или пользовательские ошибки, возбуждение которых предусматривает программист. Например, при работе с объектом пользовательского класса программист может и должен предусмотреть специальную процедуру Check, которая проверяет правильность
задания свойств объекта. Если обнаруживается, что свойства объекта заданы некорректно, так что выполнение операций над ним приведет к неверным результатам, то возбуждается собственная ошибка. Конечно, также как и для стандартных ошибок, ее тип должен быть полностью определен, задан ее номер и другие параметры. Возможно, программное обнаружение исключительных ситуаций и возбуждение собственных ошибок это наиболее важная и наиболее трудная часть программистской работы по управлению ошибками. Заметим, что какие бы ошибки не возбуждались, — внутренние или пользовательские, в момент возбуждения ошибки заполняются свойства объекта Err, так что он содержит всю информацию о последней возникшей ошибке.
Синтаксически охраняемый блок окружен специальными операторами On Error. В начале блока оператор On Error задает метку обработчика ошибки охраняемого блока. Обработчик ошибок, как правило, завершается специальным оператором Resume, который задает точку в процедуре, которой передается управление после завершения обработки ошибки. Приведем схему процедуры с тремя охраняемыми блоками:
Sub ProcWithErrors() 'Первый охраняемый блок On Error GoTo ErrHadler1 ' подключение 1-го обработчика ошибок ' Первая часть процедуры, которая может вызвать ошибку. ... On Error GoTo 0 отключение 1-го обработчика ошибок 'Второй охраняемый блок On Error GoTo ErrHadler2 ' подключение 2-го обработчика ошибок ' Вторая часть процедуры, которая может вызвать ошибку. ... On Error GoTo 0 отключение 2-го обработчика ошибок 'Третий охраняемый блок On Error GoTo ErrHadler3 ' подключение 3-го обработчика ошибок ' Третья часть процедуры, которая может вызвать ошибку. ... On Error GoTo 0 отключение 3-го обработчика ошибок RepeatPoint: ' точка, с которой возобновляется выполнение 'после обработки ошибки в 3-ей части ... Exit Sub 'выход из процедуры при отсутствии ошибок 'ОбработкаОшибок: ErrHandler1: ' 1-ый обработчик ошибок ... Resume 'возврат к оператору, вызвавшему ошибку в 1-ой части ErrHandler2: ' 2-ой обработчик ошибок ... Resume Next 'переход к оператору, следующему за оператором 'вызвавшим ошибку во 2-ой части ErrHandler3: ' 3-ий обработчик ошибок ... Resume RepeatPoint 'переход к строке, с которой возобновляется 'выполнение после обработки ошибки в 3-ей части End Sub
10.2.
Такова общая, достаточно простая схема обработки ошибок (исключений) в языке VBA. Стоит обратить внимание на то, что ситуация все же не столь проста, как может показаться с первого взгляда. Дело в том, что любой охраняемый блок может содержать вызовы процедуры процедур и функций. Поэтому реальная ситуация обычно такова, — один из операторов охраняемого блока запускает цепочку вызовов процедур и функций, каждая из которых имеет свои охраняемые блоки и свои обработчики ошибок. Ошибка может произойти на каком-то шаге в одной из вызванных процедур. Какие обработчики будут вызываться и в каком порядке, об этом поговорим чуть позже. Чтобы разобраться с деталями, вначале стоит подробно рассмотреть возможности используемых средств — операторов On Error, Resume и объекта Err с его свойствами и методами.
Оператор On Error
Имеется три варианта синтаксиса этого оператора:
On Error GoTo строка On Error Resume Next On Error GoTo 0
Рассмотрим подробно каждый из трех вариантов:
- Оператор On Error GoTo строка используется, как заголовок охраняемого блока. Его обязательный аргумент строка является либо меткой строки или номером строки, задающей начало обработчика ошибки. Заметьте, обработчик ошибки — это фрагмент кода, расположенный в той же процедуре, что и охраняемый блок. Если в охраняемом блоке возбуждается ошибка, то управление покидает охраняемый блок и передается на указанную строку, запуская, тем самым, обработчик ошибок, начинающийся в этой строке.
- Оператор On Error Resume Next также используется, как заголовок охраняемого блока. В этом случае с охраняемым блоком обработчик ошибок не связан. Точнее, он состоит из одного оператора Resume Next, включенного непосредственно в оператор On Error. При возникновении ошибки в охраняемом блоке, управление перехватывается и передается оператору, следующему за оператором, приведшему к ошибке. Конечно, такая ситуация разумна только в том случае, когда вслед за оператором, при выполнении которого потенциально возможна ошибка, программист помещает оператор, анализирующий объект Err, и в случае ошибки принимает меры по ее устранению. Это довольно типичная ситуация, когда обработка возможной ошибки заранее предусмотрена и встроена в процедуру.
- Оператор On Error GoTo 0 является закрывающей скобкой, — он завершает охраняемый блок. Выполнение оператора On Error GoTo 0 приводит также к «чистке» свойств объекта Err аналогично методу Clear этого объекта. Синтаксис оператора трудно признать удачным, фраза GoTo 0 только сбивает с толку, поскольку 0 не рассматривается как номер строки, даже если строка с номером 0 существует. Неудачным решением является и то, что этот оператор можно опускать, если охраняемый блок завершается вместе с самой процедурой. Лучше бы иметь завершающую структурную скобку, как это сделано в VBA для всех управляющих структур. В процедурах, состоящих из нескольких охраняемых блоков, применение этого оператора обязательно. Прежде чем объявить новый охраняемый блок, нужно отключить текущий оператором On Error GoTo 0.
Заметьте, если ошибка возбуждена вне охраняемого блока, то она приведет к стандартному способу ее обработки, заключающемуся в том, что будет выдано сообщение об ошибке и выполнение будет приостановлено. Взгляните, как выглядит стандартное сообщение об ошибке:
Рис.
10.14.
Стандартное сообщение об ошибке периода выполнения
Доступные кнопки в этом окне определяют возможные действия в этом случае. Все эти варианты, как правило, мало устраивают конечного пользователя. Вот еще один вариант такого сообщения:
Рис.
10.15.
Другой вид окна сообщения об ошибке периода выполнения