Основы офисного программирования и язык VBA

Отладка и оптимизация программ. Отладка

Разбить на страницы
Показывать лекцию целиком

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

Отладка

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

Можно ли создать надежную программную систему? Вспоминая опыт собственной работы, могу сказать, что в наиболее ответственных случаях, когда речь шла об экспериментах, связанных с космосом, решение заключалось в том, что программа создавалась независимо двумя коллективами, начиная от разработки алгоритма, кончая системой тестов. Только после того, как обе системы правильно работали на всех предъявленных обеими сторонами тестах, программа принималась в эксплуатацию. Это были шестидесятые годы. Сегодня мы живем в другом мире, с другими возможностями. Вот цитата из письма, которое я, как бета-тестер Office 2000 получил от команды, занимающейся отладкой этой системы:

"...Another added feature to our beta program will be the privilege to nominate other beta testers. Over our beta program we receive over 500,000 requests to participate on the Office beta program. This upcoming beta we are going to allow you, our top beta tester to add your co worker, friend or neighbor to our program".

Вряд ли здесь необходим точный перевод. Речь идет о том, что одной из привилегий лучших бета - тестеров будет возможность рекомендовать тестеров для участия в новой программе тестирования. И делается это потому, что возникла проблема отбора тестеров. Заметьте, поступило 500000 заявок на участие в тестировании программного продукта. Следует заметить, что отношение к тестерам и их работе самое серьезное. Могу сказать, что ни один из посланных мной отчетов не остался без внимания. Конечно, при такой коллективной и независимой отладке можно в гораздо большей степени надеяться на надежность программ.

Поговорим сейчас о том, что должен делать каждый из программистов, работающих в среде Office 2000, чтобы создать надежный продукт и уменьшить число возможных ошибок, не надеясь на постороннюю помощь. Мы рассмотрим три темы:

  • Как написать, по возможности, надежную программу?
  • Как вести отладку? Средства отладки Office 2000.
  • Ошибки периода исполнения и их обработка.
  • Написание надежных программ

    Ошибки неизбежно сопровождают всякую сколь-нибудь сложную программу. Это утверждение может вызвать раздражение у начинающего программиста, но человек более опытный посчитает его само собой разумеющимся и сосредоточится на средствах, предлагаемых программным окружением для борьбы с этим неизбежным злом. Самые неприятные, дорогостоящие ошибки это те, что допущены при определении основных задач и целей приложения, при проектировании структуры его управления и потоков передачи данных, а также ошибки, связанные с неверной реализацией алгоритмов. Часто они не проявляются непосредственно в виде сбоев в работе программы, а обнаруживаются после довольно длительного использования приложения и требуют для своего исправления существенных изменений в проекте и программе. Их корни могут иметь как объективную природу (сложность решаемых задач), так и субъективную (непонимание заказчиком того, что ему надо). Как бороться с такими ошибками? Выделим два известных подхода к этой проблеме. Первый из них состоит в повышении уровня языков и систем программирования, чтобы разработчик мог оперировать при создании системы понятиями предметной области, для которой она создается. Другой подход связан с идеей быстрого прототипирования, т. е. создания на ранней стадии разработки системы ее работающего прототипа, в ходе экспериментов с которым заказчик может уточнить свои требования. Оба эти подхода нашли отражение в Office 2000. Первый реализован в объектно-ориентированном подходе к построению как самой инструментальной системы, так и создаваемых в ней приложений. Развитие в VBA возможностей пользовательских модулей классов, в частности введение полиморфизма, - еще один шаг в направлении повышения уровня языка. Благодаря классам, можно вводить в программы объекты, представляющие те или иные понятия предметных областей. Реализации второго из указанных подходов существенно способствует визуальный стиль программирования, принятый во всех приложениях Office 2000. Создавая собственные меню и диалоговые окна, можно быстро спроектировать интерфейс системы и передать пользователю (тестерам) действующий ее прототип для оценки соответствия его требованиям. Затем завершить реализацию с учетом замечаний и уточнений, возникших у тестеров при работе с прототипом. Это позволит избежать многих ошибок, неизбежных при разработке системы без контактов с ее будущими пользователями.

    Одним из факторов, влияющих на надежность программ, является сам язык программирования. Известно, что язык, в котором есть объявление переменных по умолчанию, разрешены преобразования данных по умолчанию в процессе вычислений, нет строгого контроля типов, - такой язык является ненадежным, в нем значительно легче создать ненадежную программу, содержащую трудно выявляемую ошибку. Язык VBA трудно причислить к надежным языкам, он скорее занимает по шкале надежности срединное положение. Во многом, это связано с историей его возникновения. Именно поэтому надо предпринимать ряд мер, благоприятствующих повышению надежности. О многих из них мы уже говорили в разных частях этой книги. Напомним некоторые из них:

  • Проследите, чтобы все флажки на вкладке Editor из меню Tools|Options были включены. Автоматическая проверка синтаксиса в процессе написания программ, подсказка о значениях переменных, подсказка о параметрах функции, - все подключаемые свойства крайне полезны. Особое внимание обращаем на флажок "Require Variable Declaration", при включении которого в каждый модуль вставляется опция Option Explicit, принуждающая явно объявлять все переменные. С этим включенным флажком у языка VBA становится одним недостатком меньше.
  • При объявлении переменных старайтесь указать точный тип переменной и объекта. Избегайте объявлений типа Variant и Object. В этом случае на Вашей стороне будет контроль типов, что позволит избежать многих возможных ошибок.
  • При объявлении процедур явно указывайте описатели ByRef и ByVal, помните об особенностях передачи аргументов по ссылке в VBA.
  • Не забывайте о разумных размерах модулей и процедур, старайтесь создавать процедуры, модули и компоненты, допускающие переиспользование.
  • Наконец, еще раз напомним, что хорошие спецификации залог того, что программа допускает возможность изменения в процессе жизненного цикла, и что она будет корректно работать у конечного пользователя. Поэтому комментарии в тексте программы, хорошая справочная система, - все это важнейшие факторы, повышающие надежность программ.
  • (рис 10.1) Флажки вкладки Editor

    Искусство отладки

    Прелесть работы программиста во многом связана с отладкой. Почему программисты нарушают известные им требования, - не задают комментарии, не описывают детально суть решаемой задачи и не следуют другим полезным советам. Чаще всего, причина в нетерпении, им хочется скорее посмотреть, как же работает программа, увидеть результаты ее работы. Отладка - это некоторый детективный процесс. Вновь созданную программу мы подозреваем в том, что она работает не корректно. Презумпция невиновности здесь не работает. Если удается предъявить тест, на котором программа дает неверный результат, то доказано, что наши подозрения верны. Втайне мы всегда надеемся, что программа заработает правильно с первого раза. Но цель тестирования другая, - попытаться опровергнуть это предположение. И только потом, исправив все выявленные ошибки, получить корректно работающую программу. К сожалению, отладка не может гарантировать, что программа корректна, даже если все тесты прошли успешно. Отладка может доказать некорректность программы, но она не может доказать ее правильности.

    Искусство тестера состоит в том, чтобы создать по возможности полную систему тестов, проверяющую все возможные ветви вычислений. Поясним это на самом простом примере. Пусть программа находит сумму первых N элементов массива X, содержащего M элементов. Кроме "нормального" теста, проверяющего ситуацию, в которой 1<N<M, следует проверить и крайние случаи: N=1, N=M, N=0, N<0, N>M. Но это простой случай, а циклы обычно вложенные, и внутри них производится разбор случаев, внутри которых свои циклы.

    Ранее мы упоминали закон "чечако" - новичок может подвесить любую систему. Этому есть объяснение, по незнанию, он задаст одно из мало вероятных сочетаний входных данных (работая в визуальной среде, нажмет самую неподходящую для данной ситуации кнопку). Поэтому тестер, ведущий отладку, должен уметь встать и на позицию новичка, система тестов должна гарантировать, что программа корректно работает не только в "нормальных ситуациях", но и имеет "защиту от дурака" и не приведет к зацикливанию или останову в крайних, мало вероятных ситуациях.

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

    Средства отладки

    Часть ошибок программы ловится автоматически еще на этапе компиляции. Сюда относятся все синтаксические ошибки, ошибки несоответствия типов и некоторые другие. Однако синтаксически корректная программа нуждается в отладке, поскольку, хотя результаты вычислений и получены, но они не соответствуют требуемым спецификациям. Чаще всего, еще не отлаженная программа на одних исходных данных работает правильно, на других - дает ошибочный результат. Искусство отладки состоит в том, чтобы обнаружить все ситуации, в которых работа программы приводит к ошибочным вычислениям. VBA обладает весьма изощренными средствами, предназначенными для отладки программ, т.е. для обнаружения ошибок в программах (тестирования) и их исправления. Есть две группы средств VBA, помогающие программисту выявить и исправить ошибки:

  • Первая группа позволяет контролировать ход вычислительного процесса, т.е. порядок следования операторов в процедурах, порядок вызова самих процедур. При необходимости в процессе отладки разрешается изменять этот порядок, можно, например, пропускать исполнение некоторых операторов, или повторно возвращаться к их исполнению
  • Вторая группа средств позволяет контролировать изменение состояния вычислительного процесса (значений переменных и свойств объектов) в процессе выполнения. И здесь можно вмешаться и изменить состояние, задав по ходу дела новые значения для тех или иных переменных.
  • Прежде, чем приступить к подробному рассмотрению этих средств, напомним, что в ходе отладки программа может находиться в одном из трех состояний: проектирования, вычисления и прерывания. Закончив проектирование, можно запустить программу на исполнение. Прервав исполнение программы в заданной точке, перейдя в состояние прерывания, можно проконтролировать значения переменных и свойств объектов в данной точке и, если требуется, изменить эти значения "вручную". При этом можно изменить порядок выполняемых операторов, задавать следующий исполняемый оператор, можно редактировать программный текст перед продолжением вычисления. Переход из состояния вычисления в состояние прерывания может происходить по самым разным причинам, например, по достижении точки прерывания, при выполнении одного из многочисленных условий прерывания, из-за пошагового выполнения программы. Все эти возможности мы еще обсудим, а сейчас рассмотрим один особый случай. Иногда программа "зацикливается" и необходимо принудительно перевести ее в с остояние прерывания. Как остановить работающую программу? Просто нажмите знакомую еще по работе в DOS пару клавиш Ctrl+Break. На экране появится следующее диалоговое окно с сообщением об остановке.

    (рис 10.2) Сообщение о прерывании работы программы

    В этом окне нажатие кнопки "End" остановит исполнение программы, нажатие кнопки "Continue" возобновит ее выполнение, программа снова перейдет в состояние вычисления. Если нажать кнопку "Debug", то выполнение программы прервется, и мы перейдем в режим прерывания - режим отладки. Активным станет окно с текстом программы, а в нем будет выделен оператор, на котором прервалось исполнение.

    Панель отладки и команды меню

    Как и во многих других случаях, интерфейс отладчика VBA избыточен, - к одним и тем же инструментальным средствам можно добраться по-разному. В зависимости от привычек можно использовать панель Debug с инструментальными кнопками, можно использовать команды из меню Debug и View, можно использовать горячие клавиши. На следующем рисунке показана панель Debug (Отладка). Заметьте, на этой панели больше кнопок, чем в стандартном варианте, предлагаемом по умолчанию. Используя режим настройки (Customize) я вынес на эту панель дополнительные кнопки, задающие инструменты отладки.

    (рис 10.3) Панель отладки с инструментальными кнопками

    Кнопки этой панели соответствуют командам меню "Отладка" (Debug) и меню "Вид" (View), которые представлены на следующих рисунках:

    (рис 10.5) Команды меню View(рис 10.4) Команды меню Debug

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

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

    Первая из этих кнопок запускает программу на выполнение. Более точно это означает следующее. Если курсор стоит в любом месте некоторой процедуры, то на выполнение запускается эта процедура. Если активна некоторая форма, то запускается эта форма, в противном случае запускается макрос. Вторая кнопка этой группы прерывает выполнение и переводит программу в состояние прерывания. Третья кнопка приостанавливает выполнение и производит сброс проекта,- чистятся все стеки, так что происходит переход в состояние проектирования.

    Эти две кнопки позволяют работать с точками прерывания. Точки прерывания играют важную роль в процессе отладки. Чаще всего, для понимания поведения программы достаточно проанализировать состояние вычислений в некоторых ее точках, например, после завершения очередного цикла. Точки прерывания ставятся в таких контрольных точках программы, после чего программа запускается на выполнение. Достигнув первой по ходу вычисления точки прерывания, выполнение приостанавливается, программа переходит в состояние прерывания. Состояние программы анализируется и затем можно произвести запуск вычислений до следующей точки прерывания. Таким образом, точки прерывания размечают программу, являясь промежуточными финишами процесса выполнения.

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

    Важная группа кнопок, включающая средства управления процессом вычислений. С их помощью на важных участках можно проследить выполнение программы с точностью до оператора. Первая из кнопок (Step Into - ей соответствует нажатие клавиши F8) задает пошаговый, пооператорный режим выполнения. При вызове процедур и функций в этом режиме начинается их пошаговое выполнение. После выполнения очередного оператора происходит прерывание и программа доступна для корректировки. Вторая кнопка (Step Over - Shift + F8) вызов процедур и функций выполняет за один шаг, что позволяет не задерживаться на выполнении уже отлаженных модулей. Третья кнопка (Step Out - Ctrl + Shift +F8) позволяет прервать пошаговое выполнение процедуры и вернуться к этому режиму уже в вызывающей процедуре. Четвертая из этих кнопок (Run to Cursor - Ctrl + F8) позволяет установить курсор в нужную позицию и запустить программу на выполнение. Прерывание наступит, когда процесс вычислений дойдет до стр оки, указанной курсором. Заметьте, что чаще используют горячие клавиши, чем кнопки или команды меню.

    Очень полезные кнопки, по крайней мере, первая из них. С ее помощью можно изменять порядок вычислений, предписанный программой. Мы уже говорили, что в процессе отладки желтая стрелка в левом поле задает строку с текущим выполняемым оператором. VBA позволяет самому программисту устанавливать, какой оператор будет выполняться следующим, при этом, что очень важно, можно производить откат назад и возвращаться к повторному исполнению ранее выполненного оператора. Чаще всего это полезно, когда в выполняемую процедуру внесены изменения, тут же можно проанализировать эффект исправлений без того, чтобы все вычисления производить заново. Итак, если в режиме прерывания поставить курсор на любой из операторов выполняемой процедуры и щелкнуть первую из кнопок данной группы, то желтая стрелка будет перенесена к этому оператору. Этот оператор станет текущим и будет следующим выполняемым оператором. Конечно, все это можно делать в пределах выполняемой процедуры. Это замечательное для отладки свойство возможно благодаря тому, что VBA является интерпретируемым языком. Заметим, что перенос стрелки, отмечающей текущий оператор, чаще всего делается с помощью мышки простым перетаскиванием желтой стрелки в левом поле вверх или вниз к нужному оператору (строке).

    Следующая группа кнопок задает инструментальные средства, позволяющие наблюдать за состоянием программы, значениями ее переменных. Этим кнопкам соответствуют команды меню View. Первые три кнопки вызывают соответственно три окна наблюдения за состоянием вычислений - окно локальных переменных (Locals), окно проверки с отладочными результатами вычислений (Immediate), окно контрольных выражений (Watch). Следующая пара кнопок также предназначена для работы с контрольными выражениями, открывая специальные окна для показа и добавления контрольных выражений. Наконец, последняя кнопка в этой группе очень полезна при работе в ситуациях, когда отлаживаются рекурсивные процедуры или процедуры, где встречается глубокая вложенность вызовов. Она позволяет показать стек вызовов процедур в текущий момент. Чуть позже мы подробнее рассмотрим работу с этими окнами.

    При отладке весьма полезны и другие инструментальные средства, не связанные непосредственно с панелью Debug. Они остались вне нашего рассмотрения, но о некоторых из них все-таки скажем пару слов.

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

    Еще одна полезная кнопка этой панели Quick Info. Она позволяет получить подсказку о типе переменной, обо всех параметрах вызываемой функции и их типах. Для получения подсказки достаточно подвести курсор к соответствующей переменной или функции и щелкнуть кнопку. Тут же появится окно с подсказкой. Отметим также еще одно весьма полезное свойство, если просто подвести курсор к переменной, то тут же появляется окно подсказки, в котором показано значение этой переменной. Это эффективное средство наблюдения за состоянием переменных, не требующее специальных окон, наблюдение делается на лету.

    Окна наблюдения

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

    Public Sub WorkwithBinTree()
     Dim MyDict As New BinTree
     Dim englword As String, rusword As String
     GlobeVar = "Привет!"
     'Создание словаря
    
     MyDict.SearchAndInsert key:="dictionary", info:="словарь"
     MyDict.SearchAndInsert key:="hardware", info:="аппаратура, аппаратные средства"
     MyDict.SearchAndInsert key:="processor", info:="процессор"
     MyDict.SearchAndInsert key:="backup", info:="резервная копия"
     MyDict.SearchAndInsert key:="token", info:="лексема"
     MyDict.SearchAndInsert key:="file", info:="файл"
     MyDict.SearchAndInsert key:="compiler", info:="компилятор"
     MyDict.SearchAndInsert key:="account", info:="учетная запись"
    
     'Обход словаря
     MyDict.PrefixOrder
     
     'Поиск в словаре
     englword = "account": rusword = ""
     MyDict.SearchAndInsert key:=englword, info:=rusword
     Debug.Print englword, rusword
     
     'Удаление из словаря
     MyDict.DelInTree englword
     englword = "hardware"
     MyDict.DelInTree englword
     'Обход словаря
     MyDict.PrefixOrder
     
     'Debug.Print MyDict
     
    End Sub

    Окно локальных переменных - Locals

    Наиболее простой и эффективный способ следить за состоянием вычислений заключается в том, чтобы во время отладки включить окно локальных переменных (Locals). Напоминаем, для этого есть соответствующая инструментальная кнопка панели Debug и команда меню View. В этом окне будут отображены все описанные в текущей процедуре переменные, их значения и типы. Обновление окна происходит автоматически при каждом переключении из режима выполнения в режим прерывания, а также при перемещении по стеку вызовов процедур. Первая переменная в этом окне - это специальная переменная с именем модуля, которая показывает все глобальные переменные, описанные на уровне текущего модуля. Для модуля класса первая переменная имеет имя Me и показывает свойства класса. Окно разбито на три столбца для имени переменной, ее значения и типа. Таким образом, окно позволяет следить за всеми локальными переменными текущей выполняемой процедуры и частью ее глобальных переменных, тех, которые описаны в текущем модуле. Отметим также, что объекты и массивы могут быть свернуты и развернуты, используя обычную технику знаков "+" и "-".

    Давайте начнем отладку нашего примера, поставим первую точку прерывания после предварительного формирования словаря на операторе, запускающем префиксный обход дерева. Запустим программу на выполнение, и после ее останова на точке прерывания исследуем окно локальных переменных. Эта ситуация показана на следующем рисунке.

    (рис 10.6) Окно локальных переменных процедуры стандартного модуля

    Заметьте, в окне Locals показана глобальная переменная модуля Examples и все локальные переменные исполняемой процедуры WorkWithBinTree. Объект MyDict класса BinTree на рисунке частично раскрыт. Как видите, окно позволяет тщательно проанализировать достаточно сложную структуру этого объекта. В окне Locals можно не только наблюдать за значениями переменных, но и изменять эти значения, редактируя поле Value.

    Давайте нажмем клавишу F8 для выполнения следующего шага вычислений. Тем самым мы начнем исполнять метод PrefixOrder класса BinTree, вызванный объектом MyDict. Выполним в пошаговом режиме несколько операторов и остановимся после первой отладочной печати. Заметьте, что поскольку теперь исполнение идет в другом контексте, - исполняется модуль класса, то окно Locals полностью обновится и будет теперь задавать состояние исполняемого метода класса. Взгляните, как это выглядит на рисунке:

    (рис 10.7) Окно локальных переменных метода класса

    Кроме показа переменных окно Locals позволяет проанализировать текущее состояние стека вызовов. Для этого в правом верхнем углу окна есть специальная кнопка, щелчок которой дает тот же эффект, что соответствующая инструментальная кнопка меню Debug. Продемонстрируем ее использование чуть позже, а сейчас обращаем внимание, что результаты исполнения оператора Debug.Print появились в окне Immediate, к рассмотрению которого мы и переходим.

    Окно проверки - Immediate

    В этом окне появляется вся отладочная информация, поступающая в результате выполнения методов ?", после которого печатается запрашиваемое выражение. После нажатия Enter в окне появляется значение выражения. В предыдущих лекциях мы неоднократно пользовались запросами для вычисления значений различных функций. Окно позволяет изменять и значения глобальных переменных модуля. Взгляните, как выглядит окно проверки, в котором выполняются некоторые вычисления в состоянии прерывания процедуры. В частности, в этом окне дважды присваивалось новое значение глобальной переменной и выдавался запрос на получение ее значения. Здесь же был вызван на исполнение метод PostfixOrder объекта root. В этом же окне появились результаты его работы, напечатанные оператором Debug.

    (рис 10.8) Вычисления в окне проверки

    Окно контрольных выражений - Watch

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

    Наблюдение за состоянием выражений перестало быть главной функцией окна Watch. Выражения, хранящиеся в этом окне, являются контрольными выражениями (может быть, точнее было бы - контролируемыми выражениями). Контрольными могут быть любые арифметические, логические или строковые выражения. Для заданных контрольных выражений отладчик позволяет следить за:

  • Значением контрольного выражения. В этом случае контрольное выражение - это обычное наблюдаемое выражение.
  • Моментом изменения значения контрольного выражения. В этом теперь основное назначение контрольных выражений. Всякий раз, когда контрольное выражение меняет свое значение, выполнение приостанавливается на операторе, следующим за оператором, изменившим контрольное выражение. Вот пример, когда такое использование контрольных выражений крайне полезно. В сложных системах обмен информацией между модулями может осуществляться через общий пул данных - общие глобальные переменные. В этом случае бывает трудно понять, какая из процедур меняет неподходящим образом значение общедоступной глобальной переменной. Без контрольного выражения поймать такую ошибку крайне трудно.
  • Моментом, когда контрольное выражение, принимает значение True. Это вариация предыдущего случая и можно указать много ситуаций, когда обнаружение такого факта, приостанавливающего вычисления весьма полезно при отладке.
  • Следует только понимать, что злоупотреблять контрольными выражениями не следует, поскольку они замедляют процесс вычислений, поскольку перевычисляются на каждом шаге. Кроме того, по этой же причине следует аккуратно задавать контекст, в котором следует вычислять эти выражения.

    Помимо окна Watch, в котором появляются контрольные выражения, есть еще окно для добавления нового контрольного выражения, напомним, это окно может быть вызвано соответствующей инструментальной кнопкой панели Debug или, чаще всего, командой Add Watch из контекстного меню, появляющегося при нажатии правой кнопки в окне кода. Вот как выглядит это окно:

    (рис 10.9) Окно добавления контрольного выражения

    В поле "Expression" вводится новое контрольное выражение. В нашем примере - это переменная info, в общем случае это может быть любое выражение. В этом окне задается контекст, в котором выражение будет регулярно вычисляться по завершении каждого оператора. Контекст определяет процедуру, модуль и проект. В нашем примере мы указали все процедуры модуля BinTree текущего проекта. Как уже говорилось, чем шире контекст, тем больше проводится дополнительных вычислений по ходу отладки. Кроме контекста задается один из трех возможных типов контрольного выражения. Выбранный нами тип "Break When Value Changes" позволяет прервать вычисления всякий раз, когда в заданном контексте изменится значение контрольной переменной rusword. Добавленное контрольное выражение появляется в окне "Контрольные значения" (Watch).

    Просмотреть значение и добавить новое контрольное выражение можно в окне быстрого просмотра Quick Watch. Для этого достаточно выделить выражение и нажать упоминавшуюся ранее инструментальную кнопку панели Debug, выполнить команду меню или нажать комбинацию горячих клавиш - Shift +F9. Вот как выглядит это окно

    (рис 10.10) Окно быстрого просмотра контрольного выражения

    Щелкнув по кнопке Add в этом окне, мы добавили переменную MyDict в окно Watch. Сравнивая окно Watch с окном Locals, отметим, что к трем колонкам Expression, Value и Type добавился еще один столбец Context, в котором задается контекст контрольного выражения. Взгляните, как выглядят эти окна после того, как мы добавили три контрольных выражения:

    (рис 10.11) Окна отладчика после добавления контрольных выражений

    Еще один вид информации о текущем состоянии вычисления представлен стеком вызовов процедур. Имя каждой вызванной процедуры помещается на вершину этого стека в момент ее вызова и убирается из него сразу после завершения этого вызова. Просмотреть стек вызовов можно, нажав клавиши Ctrl+L или выбрав инструментальную кнопку или команду "Call Stack…" в меню View. Чаще всего, в окно "Стек вызова" попадают из окна Locals, щелкнув в нем имеющуюся для этой цели кнопку "…".

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

    (рис 10.12) Окна отладчика в момент прерывания

    Заметьте, полученной информации достаточно, чтобы понять в какой процедуре произошло изменение значения переменной info. Но эта процедура рекурсивна и вызывалась неоднократно. Поэтому хотелось бы знать, на каком шаге ее вызова произошло изменение контрольной переменной. Для этого и служит стек вызовов, окно которого и было открыто щелчком соответствующей кнопки в окне Locals.

    (рис 10.13) Окно стека вызовов

    Три вызова функции SearchAndInsert были выполнены, прежде чем изменилось значение контрольной переменной. К сожалению, в окне стека вызовов не указаны параметры соответствующих вызовов. Но получить информацию об их значениях и о состоянии локальных переменных вызовов можно. Для этого нужно выделить интересующий вызов в стеке и щелкнуть по кнопке "Show". В результате в окне Locals ,будут отображены значения локальных переменных в момент выделенного вызова.

    Разговор о средствах отладки будет не полным, если не рассмотреть специальный объект Debug.

    Объект Debug и его методы

    Объект Debug специально сконструирован для проведения отладки. Он имеет два метода Print и Assert.

    Метод Print

    Его синтаксис:

    Debug.Print список_выражений

    Метод позволяет во время выполнения программы напечатать значения выражений из списка выражений в окне проверки Immediate. Трудно представить программу в период отладки, в которой бы многократно не использовался бы метод Print объекта Debug. Во всех наших примерах демонстрировалось его применение. Мы не будем останавливаться на некоторых деталях синтаксиса и управления выводом, при желании познакомиться с ними можно обратиться к справке.

    Метод Assert

    Его синтаксис:

    Debug.Assert булево_выражение

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

    Заметьте, Assert - выражения подобны одному из типов контрольных выражений, которые также приводят к прерыванию, когда приобретают значение False. Синтаксис и выполнение отдельного оператора Debug.Assert, совершенно понятны. Однако есть смысл поговорить о том, какую роль играют Assert - утверждения в процессе отладки. Идея такова: программа разбивается на участки, каждый из которых начинается и завершается Assert - утверждением. Предполагается, что программист может задать в виде булевого выражения утверждение, которое должно быть истинно в этой точке программы. В типичном случае Assert - утверждения вставляются в начало и конец каждой процедуры и функции. В начале процедуры эти утверждения задают условия, которым должны удовлетворять исходные данные. В конце работы процедуры булевы выражения описывает требования, предъявляемые к результатам. Если начальные и конечные утверждения истинны, то полагается, что процедура была корректно вызвана и корректно завершила свою работу. Конечно же, чтобы использовать эту технику в полной мере следует иметь опыт доказательства правильности программ. Приведем пример:

    Public Function Fact2(ByVal N As Integer) As Integer
    	'Функция спроектирована для вычисления факториалов чисел, не больших 7
    	
    	Debug.Assert (N >= 0) And (N < 8)
    	
    	If (N = 0) Or (N = 1) Then	 ' базис индукции.
    		Fact2 = 1		' 0! =1.
    	Else	' рекурсивный вызов в случае N > 0.
    		Fact2 = Fact2(N - 1) * N
    	End If
    	
    	Debug.Assert Fact2 <= 5040
    	
    End Function

    Заметьте, говорить о том, правильно или неправильно работает функция Fact2, вычисляющая факториал, не имеет особого смысла, если не упоминать интервал возможных значений ее входного параметра. В узком интервале значений, для которого она и была спроектирована, она работает корректно. Вне этого интервала использовать ее не следует. Assert - утверждение в начале этой функции приостановит выполнение, если будет сделана попытка использовать эту функцию вне тех пределов, для которых она гарантирует корректную работу. Если бы этих утверждений не было, то попытка вызвать эту функцию с отрицательным N или N, большим 7, привела бы к останову выполнения программы.

    Доказательство правильности программ

    Мы уже говорили, что отладка, основанная на построении системы тестов, не может доказать правильность программы. Поэтому в теоретическом программировании были предприняты большие усилия по разработке методов доказательства правильности программ, такие же строгие, как и методы доказательства правильности теорем. На практике эти методы не получили широкого распространения по двум причинам. Во-первых, построить доказательство правильности программы сложнее, чем написать саму программу. Во-вторых, ошибки в доказательстве столь же возможны, как и в самой программе. Тем не менее, знание основ доказательства правильности программ должно быть частью образования программиста. Умение строго доказывать правильность простых программ помогает программисту лучше понять, как следует разрабатывать корректно работающие, сложные программы. Не ставя целью сколь либо полный обзор этого важного направления, остановимся лишь на самом понятии правильности программы. Действительно, мы многократно использовали этот термин, но что значит правильно (корректно) работающая программа? Вот одно из возможных определений. Пусть P(X,Y) - программа, с заданными входными данными X и результатами Y. Предикат Q(X), определенный на входных данных, будем называть предусловием программы P, а предикат R(X,Y), связывающий входные и выходные переменные будем называть постусловием программы P. Будем также предполагать, что в ходе своей работы программа не меняет своих входных переменных X.

    Программа P(X,Y) корректна по отношению к предусловию Q(X) и постусловию R(X,Y), если из истинности Q(X) до начала выполнения программы следует, что, будучи запущенной, программа завершит свою работу и по ее завершению будет истинным предикат R(X,Y). Условие корректности записывают в виде триады (триады Хоора) - Q(X) {P(X,Y)} R(X,Y)

    Уже из этого определения становится ясно, что говорить о правильности следует не вообще, а по отношению к заданным спецификациям, например, в виде предусловия и постусловия. Доказать правильность триады для сложных программ, как уже говорилось, довольно сложно. Один из методов (метод Флойда) состоит в том, что программа разбивается на участки, размеченные предикатами - Q1, Q2, …QN, R. Первый из предикатов представляет предусловие программы, последний - постусловие. Тогда доказательство корректности сводится к доказательству корректности последовательности триад:

    Q1{P1}Q2;	Q2{P2}Q3;	…QN{PN}R

    Нетрудно видеть, что введение Assert - утверждений является отражением метода Флойда. Хотя использование этих утверждений не предполагает проведения строгого доказательства, но это практически реализуемая попытка в этом направлении. Все что может быть сделано для повышения надежности программы, должно быть сделано.

    Условная компиляция и отладка

    Заметьте, что вызов методов объекта Debug может встречаться только в период отладки программы. В тот момент, когда программа передается конечному пользователю, вызов методов этого объекта не должен встречаться в работающей программе. Это понятно, поскольку в этом режиме никакие отладочные окна не появляются, нет никаких окон проверки и методу Print некуда направлять свой вывод, носящий отладочный характер. И уж тем более не должно прерываться выполнение программы, если вдруг Assert - утверждение станет ложным. Конечный пользователь просто не будет знать, что нужно делать в этом случае.

    Когда отладка завершена, вызовы методов Print и Assert не должны встречаться в выполняемой программе. Конечно, можно просто удалить эти вызовы из текста программы или закомментировать их. Однако жизненный цикл многих программ таков, что снова и снова приходится возвращаться к отладке ранее разработанной программы, внесению заплаток, созданию новой версии, введению новых возможностей. Поэтому удаление или комментирование вызовов объекта Debug не является лучшим способом. Гораздо удобнее иметь возможность включать или выключать отладочный режим при необходимости. Для этой цели и используются средства условной компиляции. Рассмотрим их применение на примере вызовов методов объекта Debug. Идея состоит в том, что все вызовы методов этого объекта заключаются в обертку, заданную специальным оператором #If условной компиляции. Этот оператор проверяет истинность выражения, заданного, как правило, константой периода компиляции. Эта константа играет роль флажка, в зависимости от ее значения и будут выполняться отладочные операторы. По-видимому, достаточно было бы одного примера, но скажем об этом чуть подробнее.

    Директива #const

    Эта директива позволяет задать константы условной компиляции. Ее синтаксис:

    #Const constname = expression

    Имя константы строится по обычным правилам, а выражение в правой части может содержать только литералы (символьные константы) и другие константы условной компиляции, соединенные знаками арифметических и логических операций за исключением операции Is. Эти константы, являются флажками и используются в операторе #If

    #If … Then … #Else директива

    Оператор If условной компиляции или директива If имеет синтаксис, похожий на обычный оператор If:

    #If expression Then
    statements
    [#ElseIf expression-n Then
    [elseifstatements]]
    [#Else
    [elsestatements]]
    #End If

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

    Приведем простой пример использования средств условной компиляции:

    'Константа conDebug включает или выключает отладочную печать
    #Const conDebug = True
    '#Const conDebug = False
    Public Sub TestDebug()
    	#If conDebug Then
    		Debug.Print "Привет!"
    	#Else
    		MsgBox ("Привет!")
    	#End If
    End Sub

    Ошибки периода выполнения и их обработка

    Итак, отладка программы завершена, последняя найденная ошибка исправлена. Теперь программа должна быть передана пользователю. Значит ли это, что в ходе работы пользователя с программой не будут возникать ошибки? Обязательно, будут! Нужно предпринять специальные меры, чтобы появление этих ошибок не приводило к неприятным последствиям. Если этого не сделать, то при возникновении ошибки на экране появляется сообщение, как правило, мало что говорящее пользователю и программа завершает свою работу. Как правило, все это сопровождается нелестными высказываниями пользователя в адрес разработчика. Заметьте, ошибки этого периода (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

    Такова общая, достаточно простая схема обработки ошибок (исключений) в языке 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) Другой вид окна сообщения об ошибке периода выполнения

    Оператор Resume

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

    Resume [0]
    Resume Next
    Resume строка

    В первом случае выполнение программы продолжается с повторного выполнения оператора, вызвавшего ошибку. Заметим, если это был оператор, инициировавший вызов процедур и функций, приведших в конечном итоге к ошибке, то управление передается этому оператору. Другими словами, управление всегда передается оператору охраняемого блока, явно или неявно инициировавшему ошибку. Вариант Resume (или Resume 0 ) естественно использовать, когда ошибка вызвана вводом неверных данных пользователем, а в обработчике ошибок у него запрашиваются новые правильные данные. Например, если в основной процедуре пользователь ввел имя несуществующего файла, в обработчике можно запросить новое имя и продолжить выполнение с повторения оператора открытия файла. Таким образом, этот вариант используется, если в обработчике ошибок устранена причина ошибки. В этом случае можно надеяться, что повторное исполнение оператора, ранее инициировавшего ошибку, теперь завершится благополучно.

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

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

    Объект Err

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

    Описание свойств объекта Err
    Свойство Значение
    Number Номер (код) ошибки. Это свойство по умолчанию.
    Source Строковое выражение, представляющее источник, в котором возникла ошибка. При ошибках в стандартном модуле оно содержит имя проекта. При ошибках в модуле класса свойство Source получает имя вида проект.класс. Конечно, хотелось бы, чтобы Source указывал источник возникновения ошибки более точно, хотя бы с точностью до имени процедуры, а лучше бы до оператора. Однако, этого пока не сделано.
    Description Строка с кратким описанием ошибки, если такая строка для кода, указанного в Number, существует. Для собственных ошибок значение этого свойства следует задавать.
    HelpFile Полное имя (включая диск и путь) файла справки VBA. Опять таки для собственных ошибок следует подготовить справочную систему и задавать путь к ней в этом свойстве.
    HelpContext Контекстный идентификатор файла справки, соответствующий ошибке с кодом, указанным в свойстве Number.
    LastDLLError Содержит системный код ошибки для последнего вызова DLL. Значение свойства LastDLLError доступно только для чтения. В лекции, посвященной работе с функциями Win32 API, подробно рассматривалось использование этого свойства.

    Рассмотрим пример, в котором возникает ошибка периода выполнения. Обработчик ошибки выдает сообщение о ней, используя свойства объекта 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 Sub

    Вот как выглядит окно сообщения, выведенное в обработчике ошибки.

    (рис 10.16) Сообщение, сформированное в обработчике ошибки

    Заметьте, после выдачи сообщения процедура нормально завершает свою работу и в окне проверки 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 Sub

    Приведем теперь процедуру, работающую с объектами, этого класса:

    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 Sub

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

    (рис 10.17) Окно диалога с пользователем

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

    09.08.99    25

    Обработчики ошибок и вложенные вызовы процедур

    К каждой процедуре может быть подключено несколько обработчиков ошибок. Этот факт был специально отражен в схеме процедуры, когда в этой лекции описывалась модель управления ошибками. При возникновении ошибки в процедуре только один из них может быть активным. Активным обработчиком является тот обработчик, который связан с охраняемым блоком, в теле которого возникла ошибка. Заметьте, несмотря на то, что подключенных обработчиков в процедуре может быть несколько, в момент возникновения ошибки в ней может не быть активного обработчика, если ошибка возникла вне охраняемых блоков.

    В процессе вычислений одни процедуры могут вызывать другие. Поэтому в момент возникновения ошибки в стеке вызовов процедур могут находиться несколько процедур: C1, C2, …Cn. Каждая из этих процедур может иметь активный обработчик ошибок. Какой же из них будет применяться для обработки ошибки? Рассмотрим применяемую стратегию обработки. Итак, пусть есть непустой стек вызовов C1, C2, …Cn, где C1 это самый внешний вызов, а Cn - самый внутренний вызов. Обработка начинается подъемом по стеку вызовов. Если в Cn имеется активный обработчик ошибки, то он и получает управление, если его нет, то в стеке проверяется следующий по порядку вызов. Если ни один из вызовов C1 - Cn не имеет активного обработчика, то выполняется стандартная обработка с выдачей сообщения об ошибке и снятия приложения. Пусть Ck - это первый, найденный в стеке вызов, для которого существует активный обработчик, и который, как было сказано, получает управление. Обработчик Ck имеет две возможности:

  • Обработать ошибку.
  • Передать обработку ошибки обработчику, выше стоящему в стеке вызовов.
  • Во втором случае, подъем по стеку вызовов может быть продолжен, пока его не захватит обработчик, способный обработать эту ошибку, или управление не попадет последнему исполняемому стандартному обработчику. Заметим, что хорошим методическим приемом является введение некоторого универсального обработчика, который бы обрабатывал все непредусмотренные другими обработчиками ошибки. Например, такой обработчик мог бы вести журнал ошибок.

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

    Структура обработчика ошибок

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

    Как правило, каждый обработчик предназначен для обнаружения и исправления ошибок некоторого класса. Поэтому, когда он встречается с ошибкой другого класса, то он не способен ее обработать. В этом случае он передает эту функцию выше стоящему обработчику. Реализуется это тем, что обработчик вызывает метод Raise с тем же номером ошибки. При возбуждении ошибки в обработчике ошибки возобновится процесс подъема по стеку вызовов и управление сможет получить следующий активный обработчик ошибки, который в свою очередь, либо обработает ошибку, либо передаст ее вверх. Заметим, что возбуждение ошибки в обработчике ошибки может быть сознательным, но может быть и из-за того, что некорректно работает сам обработчик. В любом случае возбуждение ошибки приведет к подъему по стеку вызовов.

    Типичный обработчик ошибок представляет собой оператор выбора Select, в котором каждый случай соответствует одной обрабатываемой ошибке, а для непредусмотренных ошибок происходит повторное их возбуждение и, тем самым, передача их вверх по стеку вызовов. Допустим, в охраняемом блоке процедуры ожидаются ошибки с кодами K1, K2, , Kn - обработчик ошибок этой процедуры может быть таким:

    'ErrorHandler:
    	Select Case Err.Number	' анализ кода ошибки.
    		Case K1
    		…		'обработка ошибки с кодом K1
    		Case K2
    		…		'обработка ошибки с кодом K2
    		. . .
    		Case Kn
    		…		'обработка ошибки с кодом Kn
    		Case Else
    			'Передача управления обработчику,выше стоящему в стеке вызовов
    
    			Dim intErrNum As Integer
    			intErrNum = Err.Number	'номер ошибки
    			Err.Clear			' чистка объекта Err.
    			Err.Raise Number:= intErrNum	' повторное возбуждение ошибки
    	End Select

    Метод Raise здесь используется для повторения исходной ошибки. Если произойдет ошибка, отличная от ошибок с кодами K1, K2, , Kn, управление будет передано вверх по стеку вызовов другому активному обработчику, если таковой есть. Заметьте, перед вызовом метода Raise происходит чистка объекта Err.

    Сделаем еще несколько замечаний об обработке ошибок в Office 2000:

  • Коды всех внутренних, перехватываемых ошибок можно найти в разделе справочной системы "Trappable Errors" (Перехватываемые ошибки).
  • Если ошибка выполнения возникла в некоем объекте вне VBA (например, в рабочей странице Excel) и не обработана этим объектом, а возвращена в VBA-программу, она будет автоматически преобразована VBA в ошибку с кодом 440, которая определена как "Automation Error" (Ошибка программирования объектов). Такую ошибку желательно сразу же обработать. Если же Вы хотите передать ее на обработку вверх в вызывающую процедуру, желательно возбудить ошибку со своим специальным номером, чтобы вызывающая процедура могла различать ошибки, возникающие в разных объектах.
  • Объекты Office 2000, кроме рассмотренных выше средств работы с ошибками, могут иметь дополнительные средства для их распознавания и обработки. Например, для диалоговых окон и элементов управления определено событие Error, позволяющее обрабатывать их специфические ошибки, которые не могут быть переданы в VBA. Информация об ошибках операций доступа к базам данных может быть получена с помощью объекта Error и семейства Errors из библиотеки объектов доступа к данным (DAO). Описание ошибки Microsoft Access или объекта доступа к данным можно получить по номеру ошибки методом AccessError.
  • Функция CvErr

    При работе с процедурами стандартного модуля есть еще один способ для возврата кодов ошибок, определенных пользователем. Для этой цели можно использовать функцию CVErr, возвращающую значение типа Variant с подтипом Error, которое содержит код ошибки, указанный пользователем. В вызывающей процедуре с помощью булевой функции IsError можно проверить, является ли возвращенное значение ошибкой. В следующем примере генерируется ошибка 1999, если аргумент функции Func1 является нечисловым.

    Function Func1(Number As Variant) As Variant
    	If IsNumeric(Number) Then
    		' Вычисление корректного результата.
    		Func1 = Number * Number
    	Else	'аргумент некорректен
    		Func1 = CVErr(1999) ' возвращает код ошибки
    	End If
    End Function

    Проверять корректность работы Func1 можно так.

    Sub Testfunc1()
    Dim res As Variant, arg As Variant
    	arg = 12
    	res = Func1(arg)
    	If IsError(res) Then		'проверка ошибочности результата
    		Debug.Print "Ошибка #: ", res, "аргумент : ", arg
    
    	Else
    		Debug.Print "Результат : ", res
    	End If
    	arg = "двенадцать"
    	res = Func1(arg)
    	If IsError(res) Then		'проверка ошибочности результата
    		Debug.Print "Ошибка #: ", res, "аргумент : ", arg
    
    	Else
    		Debug.Print "Результат : ", res
    	End If
    End Sub

    Приведем результаты вычислений:

    Результат :	144 
    Ошибка #:	 Error 1999	аргумент :	двенадцать

    Оптимизация программ

    Результаты работы с системой документов должны удовлетворять заданным спецификациям. Эти результаты должны быть получены в заданное время. Точный прогноз погоды на завтра требуется сегодня, получить его послезавтра становится бессмысленным. Поэтому программисту постоянно приходится заботиться не только о корректности работы своих программ, но и об их эффективности. И здесь успех, во многом, определяется решениями, принимаемыми на самом верхнем уровне разработки проекта. Самое важное решение связано с выбором подходящей среды программирования. Здесь нужно точно учитывать перспективы развития разрабатываемой системы и выбираемой среды программирования, насколько средства, выбранные Вами сегодня, смогут удовлетворять Вашим завтрашним потребностям. Другой разрез, для многих задач не менее важный, состоит в правильном выборе структур данных и алгоритмов их обработки. Нет ничего лучше хорошего алгоритма. Он, как правило, определяет эффективность решения задач. Важную роль в повышении эффективности играет и переиспользование. Ранее созданные, тщательно проверенные и эффективные компоненты, могут определить и эффективность вновь решаемой задачи. Рассмотрение всех этих вопросов, главных источников эффективности работы системы в целом, выходит за рамки данной книги. Мы остановимся сейчас на деталях, каждая из которых в отдельности не способна существенно повысить эффективность решения. При добыче золота, можно пытаться найти самородок, а можно промывать руду, добывая песчинку за песчинкой. Так и при разработке программ, песчинки, повышающие эффективность, могут принести в результате ощутимый эффект.

    Прежде, чем начать рассмотрение отдельных элементов, еще пару слов о измерениях, которые выполняет программист, для повышения эффективности своей программы. Известен эффект "bottle neck", - узких мест программы, которые могут быть причиной ее не эффективного поведения. Чтобы получить представление о том, есть ли в программе такие узкие места, требующие первостепенного вмешательства и переписывания, чаще всего требуется построение полного профиля программы. Причем и здесь важна представительная система тестов. Опять таки, мы не будем сейчас рассматривать средства построения профиля. Отметим только, что в ряде случаев можно для проведения измерений использовать функцию Timer.По ходу изложения мы несколько раз демонстрировали ее применение.

    Приемы оптимизации кода

    По ходу изложения мы не раз обращали внимание на средства, повышающие эффективность вычислений. Об этом шла речь, при рассмотрении модулей, при рассмотрении вопроса об объявлении переменных и выполнения операций над ними. Сейчас мы приведем еще несколько советов, некоторые из которых уже упоминались ранее.

    Объявление переменных

    Для уменьшения размеров требуемой памяти и ускорения выполнения операций над данными:

  • При работе с большими массивами данных, размер которых, как правило, может изменяться, используйте динамические массивы или динамические структуры данных.
  • Объявляйте переменные в строгом соответствии с их возможным типом. Не используйте без крайней необходимости тип Variant. Работа с Variant требует больше памяти и дополнительных преобразований.
  • При задании типа, как правило, используйте тип с более узкой областью определения, достаточной для представления возможных значений. Здесь могут быть исключения, специфические для VBA. Так переменные типа Integer преобразуются к типу Long, так что разумнее задавать сразу тип Long вместо типа Integer.
  • Операции с плавающей точкой выполняются более долго, поскольку требуют обращения к сопроцессору. Поэтому тип Single и Double следует использовать только в случае действительной необходимости. Иногда, можно воспользоваться типом Currency для выполнения подобных действий. В этом случае потребуется больше памяти, но не будет обращений к сопроцессору с плавающей точкой.
  • При работе с объектами следует вводить переменные соответствующего объектного типа. Они хранят ссылки на объекты, а работа со ссылками выполняется значительно быстрее, чем с самими объектами.
  • При объявлении объектных переменных избегайте объявления Object, указывайте явный тип объекта. Типы Object и Variant весьма полезные типы, но применять их нужно только в случае действительной необходимости.
  • В одном из примеров, я приводил таблицу временных измерений выполнения арифметических операций над данными разных типов. Рекомендую провести подобные замеры в более полном объеме.

    Математические операции

  • При работе с целыми используйте целочисленные операции, - деление нацело, взятие остатка и другие, не выводящие за пределы целых чисел.
  • В выражениях, где одновременно присутствуют целые числа и числа с плавающей точкой перед проведением операций всегда происходит преобразование к типам с плавающей точкой Single и Double. Для уменьшения подобных преобразований, записывайте выражения так, чтобы пересечение переменных целого и плавающего типа было, по возможности, минимальным.
  • Строковые операции

  • При работе со строками используйте введенные в Office 2000 функции Replace, функции разбора строки и другие. Ранее мы подробно рассказали об их достоинствах.
  • Избегайте, по возможности, использования конкатенации. Используйте Replace в большинстве случаев. В тех случаях, когда заменяется одна подстрока на другую такой же размерности, можно использовать функцию Mid, как в следующем примере:
    Public Sub TestCode()
    	Dim Text As String
    	Text = "Компилятор кода"
    	Mid(Text, 1, 5) = "Транс"
    	Debug.Print Text
    End Sub
  • Строковые константы VBA могут сократить время вычислений, позволяя избежать вызовов функций. Так, например, эффективнее использовать константу vbCrLf, чем комбинацию символов Chr(13), Chr(10), задающих возврат каретки и перевод строки.
  • Иногда медленные операции над строками можно изменить на операции работы с их кодами. Код
    If Asc(Text) = 202

    работает быстрее, чем код

    If Left(Text,1) = "K"

    Злоупотреблять такими оптимизациями не стоит, поскольку первый фрагмент менее понятен, чем второй. Он нуждается, по крайней мере, в комментариях.

  • Циклы

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

  • Все вычисления, которые могут быть сделаны вне цикла, должны быть вынесены из него.
  • Предусматривайте возможность досрочного завершения цикла, когда решение задачи уже получено.
  • Аккуратно работайте в цикле с элементами массивов. Старайтесь избегать лишнего вычисления индексных выражений и обращений к элементам массива. Часто введение дополнительных переменных позволяет существенно ускорить работу с элементами массивов.
  • При работе с коллекциями объектов Office 2000 используйте цикл For Each вместо обычного цикла For. В большинстве случаев это приводит к существенному выигрышу во времени исполнения цикла.
  • Завершая эту лекцию, хочу еще раз сказать, что главная оптимизация достигается на уровне проектных решений, выбора структуры данных и алгоритмов. К ниже перечисленным советам следует относиться с определенной осторожностью. Оптимизация не должна идти во вред ясности написания программ. Не всегда в ней есть необходимость. Но если такая необходимость возникает, то, в первую очередь, нужно выявить узкие места в программе и их, в основном, и оптимизировать.

    Страницы:

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

    Отладка

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

    Можно ли создать надежную программную систему? Вспоминая опыт собственной работы, могу сказать, что в наиболее ответственных случаях, когда речь шла об экспериментах, связанных с космосом, решение заключалось в том, что программа создавалась независимо двумя коллективами, начиная от разработки алгоритма, кончая системой тестов. Только после того, как обе системы правильно работали на всех предъявленных обеими сторонами тестах, программа принималась в эксплуатацию. Это были шестидесятые годы. Сегодня мы живем в другом мире, с другими возможностями. Вот цитата из письма, которое я, как бета-тестер Office 2000 получил от команды, занимающейся отладкой этой системы:

    "...Another added feature to our beta program will be the privilege to nominate other beta testers. Over our beta program we receive over 500,000 requests to participate on the Office beta program. This upcoming beta we are going to allow you, our top beta tester to add your co worker, friend or neighbor to our program".

    Вряд ли здесь необходим точный перевод. Речь идет о том, что одной из привилегий лучших бета - тестеров будет возможность рекомендовать тестеров для участия в новой программе тестирования. И делается это потому, что возникла проблема отбора тестеров. Заметьте, поступило 500000 заявок на участие в тестировании программного продукта. Следует заметить, что отношение к тестерам и их работе самое серьезное. Могу сказать, что ни один из посланных мной отчетов не остался без внимания. Конечно, при такой коллективной и независимой отладке можно в гораздо большей степени надеяться на надежность программ.

    Поговорим сейчас о том, что должен делать каждый из программистов, работающих в среде Office 2000, чтобы создать надежный продукт и уменьшить число возможных ошибок, не надеясь на постороннюю помощь. Мы рассмотрим три темы:

  • Как написать, по возможности, надежную программу?
  • Как вести отладку? Средства отладки Office 2000.
  • Ошибки периода исполнения и их обработка.
  • Написание надежных программ

    Ошибки неизбежно сопровождают всякую сколь-нибудь сложную программу. Это утверждение может вызвать раздражение у начинающего программиста, но человек более опытный посчитает его само собой разумеющимся и сосредоточится на средствах, предлагаемых программным окружением для борьбы с этим неизбежным злом. Самые неприятные, дорогостоящие ошибки это те, что допущены при определении основных задач и целей приложения, при проектировании структуры его управления и потоков передачи данных, а также ошибки, связанные с неверной реализацией алгоритмов. Часто они не проявляются непосредственно в виде сбоев в работе программы, а обнаруживаются после довольно длительного использования приложения и требуют для своего исправления существенных изменений в проекте и программе. Их корни могут иметь как объективную природу (сложность решаемых задач), так и субъективную (непонимание заказчиком того, что ему надо). Как бороться с такими ошибками? Выделим два известных подхода к этой проблеме. Первый из них состоит в повышении уровня языков и систем программирования, чтобы разработчик мог оперировать при создании системы понятиями предметной области, для которой она создается. Другой подход связан с идеей быстрого прототипирования, т. е. создания на ранней стадии разработки системы ее работающего прототипа, в ходе экспериментов с которым заказчик может уточнить свои требования. Оба эти подхода нашли отражение в Office 2000. Первый реализован в объектно-ориентированном подходе к построению как самой инструментальной системы, так и создаваемых в ней приложений. Развитие в VBA возможностей пользовательских модулей классов, в частности введение полиморфизма, - еще один шаг в направлении повышения уровня языка. Благодаря классам, можно вводить в программы объекты, представляющие те или иные понятия предметных областей. Реализации второго из указанных подходов существенно способствует визуальный стиль программирования, принятый во всех приложениях Office 2000. Создавая собственные меню и диалоговые окна, можно быстро спроектировать интерфейс системы и передать пользователю (тестерам) действующий ее прототип для оценки соответствия его требованиям. Затем завершить реализацию с учетом замечаний и уточнений, возникших у тестеров при работе с прототипом. Это позволит избежать многих ошибок, неизбежных при разработке системы без контактов с ее будущими пользователями.

    Одним из факторов, влияющих на надежность программ, является сам язык программирования. Известно, что язык, в котором есть объявление переменных по умолчанию, разрешены преобразования данных по умолчанию в процессе вычислений, нет строгого контроля типов, - такой язык является ненадежным, в нем значительно легче создать ненадежную программу, содержащую трудно выявляемую ошибку. Язык VBA трудно причислить к надежным языкам, он скорее занимает по шкале надежности срединное положение. Во многом, это связано с историей его возникновения. Именно поэтому надо предпринимать ряд мер, благоприятствующих повышению надежности. О многих из них мы уже говорили в разных частях этой книги. Напомним некоторые из них:

  • Проследите, чтобы все флажки на вкладке Editor из меню Tools|Options были включены. Автоматическая проверка синтаксиса в процессе написания программ, подсказка о значениях переменных, подсказка о параметрах функции, - все подключаемые свойства крайне полезны. Особое внимание обращаем на флажок "Require Variable Declaration", при включении которого в каждый модуль вставляется опция Option Explicit, принуждающая явно объявлять все переменные. С этим включенным флажком у языка VBA становится одним недостатком меньше.
  • При объявлении переменных старайтесь указать точный тип переменной и объекта. Избегайте объявлений типа Variant и Object. В этом случае на Вашей стороне будет контроль типов, что позволит избежать многих возможных ошибок.
  • При объявлении процедур явно указывайте описатели ByRef и ByVal, помните об особенностях передачи аргументов по ссылке в VBA.
  • Не забывайте о разумных размерах модулей и процедур, старайтесь создавать процедуры, модули и компоненты, допускающие переиспользование.
  • Наконец, еще раз напомним, что хорошие спецификации залог того, что программа допускает возможность изменения в процессе жизненного цикла, и что она будет корректно работать у конечного пользователя. Поэтому комментарии в тексте программы, хорошая справочная система, - все это важнейшие факторы, повышающие надежность программ.
  • (рис 10.1) Флажки вкладки Editor

    Искусство отладки

    Прелесть работы программиста во многом связана с отладкой. Почему программисты нарушают известные им требования, - не задают комментарии, не описывают детально суть решаемой задачи и не следуют другим полезным советам. Чаще всего, причина в нетерпении, им хочется скорее посмотреть, как же работает программа, увидеть результаты ее работы. Отладка - это некоторый детективный процесс. Вновь созданную программу мы подозреваем в том, что она работает не корректно. Презумпция невиновности здесь не работает. Если удается предъявить тест, на котором программа дает неверный результат, то доказано, что наши подозрения верны. Втайне мы всегда надеемся, что программа заработает правильно с первого раза. Но цель тестирования другая, - попытаться опровергнуть это предположение. И только потом, исправив все выявленные ошибки, получить корректно работающую программу. К сожалению, отладка не может гарантировать, что программа корректна, даже если все тесты прошли успешно. Отладка может доказать некорректность программы, но она не может доказать ее правильности.

    Искусство тестера состоит в том, чтобы создать по возможности полную систему тестов, проверяющую все возможные ветви вычислений. Поясним это на самом простом примере. Пусть программа находит сумму первых N элементов массива X, содержащего M элементов. Кроме "нормального" теста, проверяющего ситуацию, в которой 1<N<M, следует проверить и крайние случаи: N=1, N=M, N=0, N<0, N>M. Но это простой случай, а циклы обычно вложенные, и внутри них производится разбор случаев, внутри которых свои циклы.

    Ранее мы упоминали закон "чечако" - новичок может подвесить любую систему. Этому есть объяснение, по незнанию, он задаст одно из мало вероятных сочетаний входных данных (работая в визуальной среде, нажмет самую неподходящую для данной ситуации кнопку). Поэтому тестер, ведущий отладку, должен уметь встать и на позицию новичка, система тестов должна гарантировать, что программа корректно работает не только в "нормальных ситуациях", но и имеет "защиту от дурака" и не приведет к зацикливанию или останову в крайних, мало вероятных ситуациях.

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

    Средства отладки

    Часть ошибок программы ловится автоматически еще на этапе компиляции. Сюда относятся все синтаксические ошибки, ошибки несоответствия типов и некоторые другие. Однако синтаксически корректная программа нуждается в отладке, поскольку, хотя результаты вычислений и получены, но они не соответствуют требуемым спецификациям. Чаще всего, еще не отлаженная программа на одних исходных данных работает правильно, на других - дает ошибочный результат. Искусство отладки состоит в том, чтобы обнаружить все ситуации, в которых работа программы приводит к ошибочным вычислениям. VBA обладает весьма изощренными средствами, предназначенными для отладки программ, т.е. для обнаружения ошибок в программах (тестирования) и их исправления. Есть две группы средств VBA, помогающие программисту выявить и исправить ошибки:

  • Первая группа позволяет контролировать ход вычислительного процесса, т.е. порядок следования операторов в процедурах, порядок вызова самих процедур. При необходимости в процессе отладки разрешается изменять этот порядок, можно, например, пропускать исполнение некоторых операторов, или повторно возвращаться к их исполнению
  • Вторая группа средств позволяет контролировать изменение состояния вычислительного процесса (значений переменных и свойств объектов) в процессе выполнения. И здесь можно вмешаться и изменить состояние, задав по ходу дела новые значения для тех или иных переменных.
  • Прежде, чем приступить к подробному рассмотрению этих средств, напомним, что в ходе отладки программа может находиться в одном из трех состояний: проектирования, вычисления и прерывания. Закончив проектирование, можно запустить программу на исполнение. Прервав исполнение программы в заданной точке, перейдя в состояние прерывания, можно проконтролировать значения переменных и свойств объектов в данной точке и, если требуется, изменить эти значения "вручную". При этом можно изменить порядок выполняемых операторов, задавать следующий исполняемый оператор, можно редактировать программный текст перед продолжением вычисления. Переход из состояния вычисления в состояние прерывания может происходить по самым разным причинам, например, по достижении точки прерывания, при выполнении одного из многочисленных условий прерывания, из-за пошагового выполнения программы. Все эти возможности мы еще обсудим, а сейчас рассмотрим один особый случай. Иногда программа "зацикливается" и необходимо принудительно перевести ее в с остояние прерывания. Как остановить работающую программу? Просто нажмите знакомую еще по работе в DOS пару клавиш Ctrl+Break. На экране появится следующее диалоговое окно с сообщением об остановке.

    (рис 10.2) Сообщение о прерывании работы программы

    В этом окне нажатие кнопки "End" остановит исполнение программы, нажатие кнопки "Continue" возобновит ее выполнение, программа снова перейдет в состояние вычисления. Если нажать кнопку "Debug", то выполнение программы прервется, и мы перейдем в режим прерывания - режим отладки. Активным станет окно с текстом программы, а в нем будет выделен оператор, на котором прервалось исполнение.

    Панель отладки и команды меню

    Как и во многих других случаях, интерфейс отладчика VBA избыточен, - к одним и тем же инструментальным средствам можно добраться по-разному. В зависимости от привычек можно использовать панель Debug с инструментальными кнопками, можно использовать команды из меню Debug и View, можно использовать горячие клавиши. На следующем рисунке показана панель Debug (Отладка). Заметьте, на этой панели больше кнопок, чем в стандартном варианте, предлагаемом по умолчанию. Используя режим настройки (Customize) я вынес на эту панель дополнительные кнопки, задающие инструменты отладки.

    (рис 10.3) Панель отладки с инструментальными кнопками

    Кнопки этой панели соответствуют командам меню "Отладка" (Debug) и меню "Вид" (View), которые представлены на следующих рисунках:

    (рис 10.5) Команды меню View(рис 10.4) Команды меню Debug

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

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

    Первая из этих кнопок запускает программу на выполнение. Более точно это означает следующее. Если курсор стоит в любом месте некоторой процедуры, то на выполнение запускается эта процедура. Если активна некоторая форма, то запускается эта форма, в противном случае запускается макрос. Вторая кнопка этой группы прерывает выполнение и переводит программу в состояние прерывания. Третья кнопка приостанавливает выполнение и производит сброс проекта,- чистятся все стеки, так что происходит переход в состояние проектирования.

    Эти две кнопки позволяют работать с точками прерывания. Точки прерывания играют важную роль в процессе отладки. Чаще всего, для понимания поведения программы достаточно проанализировать состояние вычислений в некоторых ее точках, например, после завершения очередного цикла. Точки прерывания ставятся в таких контрольных точках программы, после чего программа запускается на выполнение. Достигнув первой по ходу вычисления точки прерывания, выполнение приостанавливается, программа переходит в состояние прерывания. Состояние программы анализируется и затем можно произвести запуск вычислений до следующей точки прерывания. Таким образом, точки прерывания размечают программу, являясь промежуточными финишами процесса выполнения.

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

    Важная группа кнопок, включающая средства управления процессом вычислений. С их помощью на важных участках можно проследить выполнение программы с точностью до оператора. Первая из кнопок (Step Into - ей соответствует нажатие клавиши F8) задает пошаговый, пооператорный режим выполнения. При вызове процедур и функций в этом режиме начинается их пошаговое выполнение. После выполнения очередного оператора происходит прерывание и программа доступна для корректировки. Вторая кнопка (Step Over - Shift + F8) вызов процедур и функций выполняет за один шаг, что позволяет не задерживаться на выполнении уже отлаженных модулей. Третья кнопка (Step Out - Ctrl + Shift +F8) позволяет прервать пошаговое выполнение процедуры и вернуться к этому режиму уже в вызывающей процедуре. Четвертая из этих кнопок (Run to Cursor - Ctrl + F8) позволяет установить курсор в нужную позицию и запустить программу на выполнение. Прерывание наступит, когда процесс вычислений дойдет до стр оки, указанной курсором. Заметьте, что чаще используют горячие клавиши, чем кнопки или команды меню.

    Очень полезные кнопки, по крайней мере, первая из них. С ее помощью можно изменять порядок вычислений, предписанный программой. Мы уже говорили, что в процессе отладки желтая стрелка в левом поле задает строку с текущим выполняемым оператором. VBA позволяет самому программисту устанавливать, какой оператор будет выполняться следующим, при этом, что очень важно, можно производить откат назад и возвращаться к повторному исполнению ранее выполненного оператора. Чаще всего это полезно, когда в выполняемую процедуру внесены изменения, тут же можно проанализировать эффект исправлений без того, чтобы все вычисления производить заново. Итак, если в режиме прерывания поставить курсор на любой из операторов выполняемой процедуры и щелкнуть первую из кнопок данной группы, то желтая стрелка будет перенесена к этому оператору. Этот оператор станет текущим и будет следующим выполняемым оператором. Конечно, все это можно делать в пределах выполняемой процедуры. Это замечательное для отладки свойство возможно благодаря тому, что VBA является интерпретируемым языком. Заметим, что перенос стрелки, отмечающей текущий оператор, чаще всего делается с помощью мышки простым перетаскиванием желтой стрелки в левом поле вверх или вниз к нужному оператору (строке).

    Следующая группа кнопок задает инструментальные средства, позволяющие наблюдать за состоянием программы, значениями ее переменных. Этим кнопкам соответствуют команды меню View. Первые три кнопки вызывают соответственно три окна наблюдения за состоянием вычислений - окно локальных переменных (Locals), окно проверки с отладочными результатами вычислений (Immediate), окно контрольных выражений (Watch). Следующая пара кнопок также предназначена для работы с контрольными выражениями, открывая специальные окна для показа и добавления контрольных выражений. Наконец, последняя кнопка в этой группе очень полезна при работе в ситуациях, когда отлаживаются рекурсивные процедуры или процедуры, где встречается глубокая вложенность вызовов. Она позволяет показать стек вызовов процедур в текущий момент. Чуть позже мы подробнее рассмотрим работу с этими окнами.

    При отладке весьма полезны и другие инструментальные средства, не связанные непосредственно с панелью Debug. Они остались вне нашего рассмотрения, но о некоторых из них все-таки скажем пару слов.

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

    Еще одна полезная кнопка этой панели Quick Info. Она позволяет получить подсказку о типе переменной, обо всех параметрах вызываемой функции и их типах. Для получения подсказки достаточно подвести курсор к соответствующей переменной или функции и щелкнуть кнопку. Тут же появится окно с подсказкой. Отметим также еще одно весьма полезное свойство, если просто подвести курсор к переменной, то тут же появляется окно подсказки, в котором показано значение этой переменной. Это эффективное средство наблюдения за состоянием переменных, не требующее специальных окон, наблюдение делается на лету.

    Окна наблюдения

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

    Public Sub WorkwithBinTree()
     Dim MyDict As New BinTree
     Dim englword As String, rusword As String
     GlobeVar = "Привет!"
     'Создание словаря
    
     MyDict.SearchAndInsert key:="dictionary", info:="словарь"
     MyDict.SearchAndInsert key:="hardware", info:="аппаратура, аппаратные средства"
     MyDict.SearchAndInsert key:="processor", info:="процессор"
     MyDict.SearchAndInsert key:="backup", info:="резервная копия"
     MyDict.SearchAndInsert key:="token", info:="лексема"
     MyDict.SearchAndInsert key:="file", info:="файл"
     MyDict.SearchAndInsert key:="compiler", info:="компилятор"
     MyDict.SearchAndInsert key:="account", info:="учетная запись"
    
     'Обход словаря
     MyDict.PrefixOrder
     
     'Поиск в словаре
     englword = "account": rusword = ""
     MyDict.SearchAndInsert key:=englword, info:=rusword
     Debug.Print englword, rusword
     
     'Удаление из словаря
     MyDict.DelInTree englword
     englword = "hardware"
     MyDict.DelInTree englword
     'Обход словаря
     MyDict.PrefixOrder
     
     'Debug.Print MyDict
     
    End Sub

    Окно локальных переменных - Locals

    Наиболее простой и эффективный способ следить за состоянием вычислений заключается в том, чтобы во время отладки включить окно локальных переменных (Locals). Напоминаем, для этого есть соответствующая инструментальная кнопка панели Debug и команда меню View. В этом окне будут отображены все описанные в текущей процедуре переменные, их значения и типы. Обновление окна происходит автоматически при каждом переключении из режима выполнения в режим прерывания, а также при перемещении по стеку вызовов процедур. Первая переменная в этом окне - это специальная переменная с именем модуля, которая показывает все глобальные переменные, описанные на уровне текущего модуля. Для модуля класса первая переменная имеет имя Me и показывает свойства класса. Окно разбито на три столбца для имени переменной, ее значения и типа. Таким образом, окно позволяет следить за всеми локальными переменными текущей выполняемой процедуры и частью ее глобальных переменных, тех, которые описаны в текущем модуле. Отметим также, что объекты и массивы могут быть свернуты и развернуты, используя обычную технику знаков "+" и "-".

    Давайте начнем отладку нашего примера, поставим первую точку прерывания после предварительного формирования словаря на операторе, запускающем префиксный обход дерева. Запустим программу на выполнение, и после ее останова на точке прерывания исследуем окно локальных переменных. Эта ситуация показана на следующем рисунке.

    (рис 10.6) Окно локальных переменных процедуры стандартного модуля

    Заметьте, в окне Locals показана глобальная переменная модуля Examples и все локальные переменные исполняемой процедуры WorkWithBinTree. Объект MyDict класса BinTree на рисунке частично раскрыт. Как видите, окно позволяет тщательно проанализировать достаточно сложную структуру этого объекта. В окне Locals можно не только наблюдать за значениями переменных, но и изменять эти значения, редактируя поле Value.

    Давайте нажмем клавишу F8 для выполнения следующего шага вычислений. Тем самым мы начнем исполнять метод PrefixOrder класса BinTree, вызванный объектом MyDict. Выполним в пошаговом режиме несколько операторов и остановимся после первой отладочной печати. Заметьте, что поскольку теперь исполнение идет в другом контексте, - исполняется модуль класса, то окно Locals полностью обновится и будет теперь задавать состояние исполняемого метода класса. Взгляните, как это выглядит на рисунке:

    (рис 10.7) Окно локальных переменных метода класса

    Кроме показа переменных окно Locals позволяет проанализировать текущее состояние стека вызовов. Для этого в правом верхнем углу окна есть специальная кнопка, щелчок которой дает тот же эффект, что соответствующая инструментальная кнопка меню Debug. Продемонстрируем ее использование чуть позже, а сейчас обращаем внимание, что результаты исполнения оператора Debug.Print появились в окне Immediate, к рассмотрению которого мы и переходим.

    Окно проверки - Immediate

    В этом окне появляется вся отладочная информация, поступающая в результате выполнения методов ?", после которого печатается запрашиваемое выражение. После нажатия Enter в окне появляется значение выражения. В предыдущих лекциях мы неоднократно пользовались запросами для вычисления значений различных функций. Окно позволяет изменять и значения глобальных переменных модуля. Взгляните, как выглядит окно проверки, в котором выполняются некоторые вычисления в состоянии прерывания процедуры. В частности, в этом окне дважды присваивалось новое значение глобальной переменной и выдавался запрос на получение ее значения. Здесь же был вызван на исполнение метод PostfixOrder объекта root. В этом же окне появились результаты его работы, напечатанные оператором Debug.

    (рис 10.8) Вычисления в окне проверки

    Окно контрольных выражений - Watch

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

    Наблюдение за состоянием выражений перестало быть главной функцией окна Watch. Выражения, хранящиеся в этом окне, являются контрольными выражениями (может быть, точнее было бы - контролируемыми выражениями). Контрольными могут быть любые арифметические, логические или строковые выражения. Для заданных контрольных выражений отладчик позволяет следить за:

  • Значением контрольного выражения. В этом случае контрольное выражение - это обычное наблюдаемое выражение.
  • Моментом изменения значения контрольного выражения. В этом теперь основное назначение контрольных выражений. Всякий раз, когда контрольное выражение меняет свое значение, выполнение приостанавливается на операторе, следующим за оператором, изменившим контрольное выражение. Вот пример, когда такое использование контрольных выражений крайне полезно. В сложных системах обмен информацией между модулями может осуществляться через общий пул данных - общие глобальные переменные. В этом случае бывает трудно понять, какая из процедур меняет неподходящим образом значение общедоступной глобальной переменной. Без контрольного выражения поймать такую ошибку крайне трудно.
  • Моментом, когда контрольное выражение, принимает значение True. Это вариация предыдущего случая и можно указать много ситуаций, когда обнаружение такого факта, приостанавливающего вычисления весьма полезно при отладке.
  • Следует только понимать, что злоупотреблять контрольными выражениями не следует, поскольку они замедляют процесс вычислений, поскольку перевычисляются на каждом шаге. Кроме того, по этой же причине следует аккуратно задавать контекст, в котором следует вычислять эти выражения.

    Помимо окна Watch, в котором появляются контрольные выражения, есть еще окно для добавления нового контрольного выражения, напомним, это окно может быть вызвано соответствующей инструментальной кнопкой панели Debug или, чаще всего, командой Add Watch из контекстного меню, появляющегося при нажатии правой кнопки в окне кода. Вот как выглядит это окно:

    (рис 10.9) Окно добавления контрольного выражения

    В поле "Expression" вводится новое контрольное выражение. В нашем примере - это переменная info, в общем случае это может быть любое выражение. В этом окне задается контекст, в котором выражение будет регулярно вычисляться по завершении каждого оператора. Контекст определяет процедуру, модуль и проект. В нашем примере мы указали все процедуры модуля BinTree текущего проекта. Как уже говорилось, чем шире контекст, тем больше проводится дополнительных вычислений по ходу отладки. Кроме контекста задается один из трех возможных типов контрольного выражения. Выбранный нами тип "Break When Value Changes" позволяет прервать вычисления всякий раз, когда в заданном контексте изменится значение контрольной переменной rusword. Добавленное контрольное выражение появляется в окне "Контрольные значения" (Watch).

    Просмотреть значение и добавить новое контрольное выражение можно в окне быстрого просмотра Quick Watch. Для этого достаточно выделить выражение и нажать упоминавшуюся ранее инструментальную кнопку панели Debug, выполнить команду меню или нажать комбинацию горячих клавиш - Shift +F9. Вот как выглядит это окно

    (рис 10.10) Окно быстрого просмотра контрольного выражения

    Щелкнув по кнопке Add в этом окне, мы добавили переменную MyDict в окно Watch. Сравнивая окно Watch с окном Locals, отметим, что к трем колонкам Expression, Value и Type добавился еще один столбец Context, в котором задается контекст контрольного выражения. Взгляните, как выглядят эти окна после того, как мы добавили три контрольных выражения:

    (рис 10.11) Окна отладчика после добавления контрольных выражений

    Еще один вид информации о текущем состоянии вычисления представлен стеком вызовов процедур. Имя каждой вызванной процедуры помещается на вершину этого стека в момент ее вызова и убирается из него сразу после завершения этого вызова. Просмотреть стек вызовов можно, нажав клавиши Ctrl+L или выбрав инструментальную кнопку или команду "Call Stack…" в меню View. Чаще всего, в окно "Стек вызова" попадают из окна Locals, щелкнув в нем имеющуюся для этой цели кнопку "…".

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

    (рис 10.12) Окна отладчика в момент прерывания

    Заметьте, полученной информации достаточно, чтобы понять в какой процедуре произошло изменение значения переменной info. Но эта процедура рекурсивна и вызывалась неоднократно. Поэтому хотелось бы знать, на каком шаге ее вызова произошло изменение контрольной переменной. Для этого и служит стек вызовов, окно которого и было открыто щелчком соответствующей кнопки в окне Locals.

    (рис 10.13) Окно стека вызовов

    Три вызова функции SearchAndInsert были выполнены, прежде чем изменилось значение контрольной переменной. К сожалению, в окне стека вызовов не указаны параметры соответствующих вызовов. Но получить информацию об их значениях и о состоянии локальных переменных вызовов можно. Для этого нужно выделить интересующий вызов в стеке и щелкнуть по кнопке "Show". В результате в окне Locals ,будут отображены значения локальных переменных в момент выделенного вызова.

    Разговор о средствах отладки будет не полным, если не рассмотреть специальный объект Debug.

    Объект Debug и его методы

    Объект Debug специально сконструирован для проведения отладки. Он имеет два метода Print и Assert.

    Метод Print

    Его синтаксис:

    Debug.Print список_выражений

    Метод позволяет во время выполнения программы напечатать значения выражений из списка выражений в окне проверки Immediate. Трудно представить программу в период отладки, в которой бы многократно не использовался бы метод Print объекта Debug. Во всех наших примерах демонстрировалось его применение. Мы не будем останавливаться на некоторых деталях синтаксиса и управления выводом, при желании познакомиться с ними можно обратиться к справке.

    Метод Assert

    Его синтаксис:

    Debug.Assert булево_выражение

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

    Заметьте, Assert - выражения подобны одному из типов контрольных выражений, которые также приводят к прерыванию, когда приобретают значение False. Синтаксис и выполнение отдельного оператора Debug.Assert, совершенно понятны. Однако есть смысл поговорить о том, какую роль играют Assert - утверждения в процессе отладки. Идея такова: программа разбивается на участки, каждый из которых начинается и завершается Assert - утверждением. Предполагается, что программист может задать в виде булевого выражения утверждение, которое должно быть истинно в этой точке программы. В типичном случае Assert - утверждения вставляются в начало и конец каждой процедуры и функции. В начале процедуры эти утверждения задают условия, которым должны удовлетворять исходные данные. В конце работы процедуры булевы выражения описывает требования, предъявляемые к результатам. Если начальные и конечные утверждения истинны, то полагается, что процедура была корректно вызвана и корректно завершила свою работу. Конечно же, чтобы использовать эту технику в полной мере следует иметь опыт доказательства правильности программ. Приведем пример:

    Public Function Fact2(ByVal N As Integer) As Integer
    	'Функция спроектирована для вычисления факториалов чисел, не больших 7
    	
    	Debug.Assert (N >= 0) And (N < 8)
    	
    	If (N = 0) Or (N = 1) Then	 ' базис индукции.
    		Fact2 = 1		' 0! =1.
    	Else	' рекурсивный вызов в случае N > 0.
    		Fact2 = Fact2(N - 1) * N
    	End If
    	
    	Debug.Assert Fact2 <= 5040
    	
    End Function

    Заметьте, говорить о том, правильно или неправильно работает функция Fact2, вычисляющая факториал, не имеет особого смысла, если не упоминать интервал возможных значений ее входного параметра. В узком интервале значений, для которого она и была спроектирована, она работает корректно. Вне этого интервала использовать ее не следует. Assert - утверждение в начале этой функции приостановит выполнение, если будет сделана попытка использовать эту функцию вне тех пределов, для которых она гарантирует корректную работу. Если бы этих утверждений не было, то попытка вызвать эту функцию с отрицательным N или N, большим 7, привела бы к останову выполнения программы.

    Доказательство правильности программ

    Мы уже говорили, что отладка, основанная на построении системы тестов, не может доказать правильность программы. Поэтому в теоретическом программировании были предприняты большие усилия по разработке методов доказательства правильности программ, такие же строгие, как и методы доказательства правильности теорем. На практике эти методы не получили широкого распространения по двум причинам. Во-первых, построить доказательство правильности программы сложнее, чем написать саму программу. Во-вторых, ошибки в доказательстве столь же возможны, как и в самой программе. Тем не менее, знание основ доказательства правильности программ должно быть частью образования программиста. Умение строго доказывать правильность простых программ помогает программисту лучше понять, как следует разрабатывать корректно работающие, сложные программы. Не ставя целью сколь либо полный обзор этого важного направления, остановимся лишь на самом понятии правильности программы. Действительно, мы многократно использовали этот термин, но что значит правильно (корректно) работающая программа? Вот одно из возможных определений. Пусть P(X,Y) - программа, с заданными входными данными X и результатами Y. Предикат Q(X), определенный на входных данных, будем называть предусловием программы P, а предикат R(X,Y), связывающий входные и выходные переменные будем называть постусловием программы P. Будем также предполагать, что в ходе своей работы программа не меняет своих входных переменных X.

    Программа P(X,Y) корректна по отношению к предусловию Q(X) и постусловию R(X,Y), если из истинности Q(X) до начала выполнения программы следует, что, будучи запущенной, программа завершит свою работу и по ее завершению будет истинным предикат R(X,Y). Условие корректности записывают в виде триады (триады Хоора) - Q(X) {P(X,Y)} R(X,Y)

    Уже из этого определения становится ясно, что говорить о правильности следует не вообще, а по отношению к заданным спецификациям, например, в виде предусловия и постусловия. Доказать правильность триады для сложных программ, как уже говорилось, довольно сложно. Один из методов (метод Флойда) состоит в том, что программа разбивается на участки, размеченные предикатами - Q1, Q2, …QN, R. Первый из предикатов представляет предусловие программы, последний - постусловие. Тогда доказательство корректности сводится к доказательству корректности последовательности триад:

    Q1{P1}Q2;	Q2{P2}Q3;	…QN{PN}R

    Нетрудно видеть, что введение Assert - утверждений является отражением метода Флойда. Хотя использование этих утверждений не предполагает проведения строгого доказательства, но это практически реализуемая попытка в этом направлении. Все что может быть сделано для повышения надежности программы, должно быть сделано.

    Условная компиляция и отладка

    Заметьте, что вызов методов объекта Debug может встречаться только в период отладки программы. В тот момент, когда программа передается конечному пользователю, вызов методов этого объекта не должен встречаться в работающей программе. Это понятно, поскольку в этом режиме никакие отладочные окна не появляются, нет никаких окон проверки и методу Print некуда направлять свой вывод, носящий отладочный характер. И уж тем более не должно прерываться выполнение программы, если вдруг Assert - утверждение станет ложным. Конечный пользователь просто не будет знать, что нужно делать в этом случае.

    Когда отладка завершена, вызовы методов Print и Assert не должны встречаться в выполняемой программе. Конечно, можно просто удалить эти вызовы из текста программы или закомментировать их. Однако жизненный цикл многих программ таков, что снова и снова приходится возвращаться к отладке ранее разработанной программы, внесению заплаток, созданию новой версии, введению новых возможностей. Поэтому удаление или комментирование вызовов объекта Debug не является лучшим способом. Гораздо удобнее иметь возможность включать или выключать отладочный режим при необходимости. Для этой цели и используются средства условной компиляции. Рассмотрим их применение на примере вызовов методов объекта Debug. Идея состоит в том, что все вызовы методов этого объекта заключаются в обертку, заданную специальным оператором #If условной компиляции. Этот оператор проверяет истинность выражения, заданного, как правило, константой периода компиляции. Эта константа играет роль флажка, в зависимости от ее значения и будут выполняться отладочные операторы. По-видимому, достаточно было бы одного примера, но скажем об этом чуть подробнее.

    Директива #const

    Эта директива позволяет задать константы условной компиляции. Ее синтаксис:

    #Const constname = expression

    Имя константы строится по обычным правилам, а выражение в правой части может содержать только литералы (символьные константы) и другие константы условной компиляции, соединенные знаками арифметических и логических операций за исключением операции Is. Эти константы, являются флажками и используются в операторе #If

    #If … Then … #Else директива

    Оператор If условной компиляции или директива If имеет синтаксис, похожий на обычный оператор If:

    #If expression Then
    statements
    [#ElseIf expression-n Then
    [elseifstatements]]
    [#Else
    [elsestatements]]
    #End If

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

    Приведем простой пример использования средств условной компиляции:

    'Константа conDebug включает или выключает отладочную печать
    #Const conDebug = True
    '#Const conDebug = False
    Public Sub TestDebug()
    	#If conDebug Then
    		Debug.Print "Привет!"
    	#Else
    		MsgBox ("Привет!")
    	#End If
    End Sub

    Ошибки периода выполнения и их обработка

    Итак, отладка программы завершена, последняя найденная ошибка исправлена. Теперь программа должна быть передана пользователю. Значит ли это, что в ходе работы пользователя с программой не будут возникать ошибки? Обязательно, будут! Нужно предпринять специальные меры, чтобы появление этих ошибок не приводило к неприятным последствиям. Если этого не сделать, то при возникновении ошибки на экране появляется сообщение, как правило, мало что говорящее пользователю и программа завершает свою работу. Как правило, все это сопровождается нелестными высказываниями пользователя в адрес разработчика. Заметьте, ошибки этого периода (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

    Такова общая, достаточно простая схема обработки ошибок (исключений) в языке 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) Другой вид окна сообщения об ошибке периода выполнения

    Оператор Resume

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

    Resume [0]
    Resume Next
    Resume строка

    В первом случае выполнение программы продолжается с повторного выполнения оператора, вызвавшего ошибку. Заметим, если это был оператор, инициировавший вызов процедур и функций, приведших в конечном итоге к ошибке, то управление передается этому оператору. Другими словами, управление всегда передается оператору охраняемого блока, явно или неявно инициировавшему ошибку. Вариант Resume (или Resume 0 ) естественно использовать, когда ошибка вызвана вводом неверных данных пользователем, а в обработчике ошибок у него запрашиваются новые правильные данные. Например, если в основной процедуре пользователь ввел имя несуществующего файла, в обработчике можно запросить новое имя и продолжить выполнение с повторения оператора открытия файла. Таким образом, этот вариант используется, если в обработчике ошибок устранена причина ошибки. В этом случае можно надеяться, что повторное исполнение оператора, ранее инициировавшего ошибку, теперь завершится благополучно.

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

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

    Объект Err

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

    Описание свойств объекта Err
    Свойство Значение
    Number Номер (код) ошибки. Это свойство по умолчанию.
    Source Строковое выражение, представляющее источник, в котором возникла ошибка. При ошибках в стандартном модуле оно содержит имя проекта. При ошибках в модуле класса свойство Source получает имя вида проект.класс. Конечно, хотелось бы, чтобы Source указывал источник возникновения ошибки более точно, хотя бы с точностью до имени процедуры, а лучше бы до оператора. Однако, этого пока не сделано.
    Description Строка с кратким описанием ошибки, если такая строка для кода, указанного в Number, существует. Для собственных ошибок значение этого свойства следует задавать.
    HelpFile Полное имя (включая диск и путь) файла справки VBA. Опять таки для собственных ошибок следует подготовить справочную систему и задавать путь к ней в этом свойстве.
    HelpContext Контекстный идентификатор файла справки, соответствующий ошибке с кодом, указанным в свойстве Number.
    LastDLLError Содержит системный код ошибки для последнего вызова DLL. Значение свойства LastDLLError доступно только для чтения. В лекции, посвященной работе с функциями Win32 API, подробно рассматривалось использование этого свойства.

    Рассмотрим пример, в котором возникает ошибка периода выполнения. Обработчик ошибки выдает сообщение о ней, используя свойства объекта 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 Sub

    Вот как выглядит окно сообщения, выведенное в обработчике ошибки.

    (рис 10.16) Сообщение, сформированное в обработчике ошибки

    Заметьте, после выдачи сообщения процедура нормально завершает свою работу и в окне проверки 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 Sub

    Приведем теперь процедуру, работающую с объектами, этого класса:

    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 Sub

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

    (рис 10.17) Окно диалога с пользователем

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

    09.08.99    25

    Обработчики ошибок и вложенные вызовы процедур

    К каждой процедуре может быть подключено несколько обработчиков ошибок. Этот факт был специально отражен в схеме процедуры, когда в этой лекции описывалась модель управления ошибками. При возникновении ошибки в процедуре только один из них может быть активным. Активным обработчиком является тот обработчик, который связан с охраняемым блоком, в теле которого возникла ошибка. Заметьте, несмотря на то, что подключенных обработчиков в процедуре может быть несколько, в момент возникновения ошибки в ней может не быть активного обработчика, если ошибка возникла вне охраняемых блоков.

    В процессе вычислений одни процедуры могут вызывать другие. Поэтому в момент возникновения ошибки в стеке вызовов процедур могут находиться несколько процедур: C1, C2, …Cn. Каждая из этих процедур может иметь активный обработчик ошибок. Какой же из них будет применяться для обработки ошибки? Рассмотрим применяемую стратегию обработки. Итак, пусть есть непустой стек вызовов C1, C2, …Cn, где C1 это самый внешний вызов, а Cn - самый внутренний вызов. Обработка начинается подъемом по стеку вызовов. Если в Cn имеется активный обработчик ошибки, то он и получает управление, если его нет, то в стеке проверяется следующий по порядку вызов. Если ни один из вызовов C1 - Cn не имеет активного обработчика, то выполняется стандартная обработка с выдачей сообщения об ошибке и снятия приложения. Пусть Ck - это первый, найденный в стеке вызов, для которого существует активный обработчик, и который, как было сказано, получает управление. Обработчик Ck имеет две возможности:

  • Обработать ошибку.
  • Передать обработку ошибки обработчику, выше стоящему в стеке вызовов.
  • Во втором случае, подъем по стеку вызовов может быть продолжен, пока его не захватит обработчик, способный обработать эту ошибку, или управление не попадет последнему исполняемому стандартному обработчику. Заметим, что хорошим методическим приемом является введение некоторого универсального обработчика, который бы обрабатывал все непредусмотренные другими обработчиками ошибки. Например, такой обработчик мог бы вести журнал ошибок.

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

    Структура обработчика ошибок

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

    Как правило, каждый обработчик предназначен для обнаружения и исправления ошибок некоторого класса. Поэтому, когда он встречается с ошибкой другого класса, то он не способен ее обработать. В этом случае он передает эту функцию выше стоящему обработчику. Реализуется это тем, что обработчик вызывает метод Raise с тем же номером ошибки. При возбуждении ошибки в обработчике ошибки возобновится процесс подъема по стеку вызовов и управление сможет получить следующий активный обработчик ошибки, который в свою очередь, либо обработает ошибку, либо передаст ее вверх. Заметим, что возбуждение ошибки в обработчике ошибки может быть сознательным, но может быть и из-за того, что некорректно работает сам обработчик. В любом случае возбуждение ошибки приведет к подъему по стеку вызовов.

    Типичный обработчик ошибок представляет собой оператор выбора Select, в котором каждый случай соответствует одной обрабатываемой ошибке, а для непредусмотренных ошибок происходит повторное их возбуждение и, тем самым, передача их вверх по стеку вызовов. Допустим, в охраняемом блоке процедуры ожидаются ошибки с кодами K1, K2, , Kn - обработчик ошибок этой процедуры может быть таким:

    'ErrorHandler:
    	Select Case Err.Number	' анализ кода ошибки.
    		Case K1
    		…		'обработка ошибки с кодом K1
    		Case K2
    		…		'обработка ошибки с кодом K2
    		. . .
    		Case Kn
    		…		'обработка ошибки с кодом Kn
    		Case Else
    			'Передача управления обработчику,выше стоящему в стеке вызовов
    
    			Dim intErrNum As Integer
    			intErrNum = Err.Number	'номер ошибки
    			Err.Clear			' чистка объекта Err.
    			Err.Raise Number:= intErrNum	' повторное возбуждение ошибки
    	End Select

    Метод Raise здесь используется для повторения исходной ошибки. Если произойдет ошибка, отличная от ошибок с кодами K1, K2, , Kn, управление будет передано вверх по стеку вызовов другому активному обработчику, если таковой есть. Заметьте, перед вызовом метода Raise происходит чистка объекта Err.

    Сделаем еще несколько замечаний об обработке ошибок в Office 2000:

  • Коды всех внутренних, перехватываемых ошибок можно найти в разделе справочной системы "Trappable Errors" (Перехватываемые ошибки).
  • Если ошибка выполнения возникла в некоем объекте вне VBA (например, в рабочей странице Excel) и не обработана этим объектом, а возвращена в VBA-программу, она будет автоматически преобразована VBA в ошибку с кодом 440, которая определена как "Automation Error" (Ошибка программирования объектов). Такую ошибку желательно сразу же обработать. Если же Вы хотите передать ее на обработку вверх в вызывающую процедуру, желательно возбудить ошибку со своим специальным номером, чтобы вызывающая процедура могла различать ошибки, возникающие в разных объектах.
  • Объекты Office 2000, кроме рассмотренных выше средств работы с ошибками, могут иметь дополнительные средства для их распознавания и обработки. Например, для диалоговых окон и элементов управления определено событие Error, позволяющее обрабатывать их специфические ошибки, которые не могут быть переданы в VBA. Информация об ошибках операций доступа к базам данных может быть получена с помощью объекта Error и семейства Errors из библиотеки объектов доступа к данным (DAO). Описание ошибки Microsoft Access или объекта доступа к данным можно получить по номеру ошибки методом AccessError.
  • Функция CvErr

    При работе с процедурами стандартного модуля есть еще один способ для возврата кодов ошибок, определенных пользователем. Для этой цели можно использовать функцию CVErr, возвращающую значение типа Variant с подтипом Error, которое содержит код ошибки, указанный пользователем. В вызывающей процедуре с помощью булевой функции IsError можно проверить, является ли возвращенное значение ошибкой. В следующем примере генерируется ошибка 1999, если аргумент функции Func1 является нечисловым.

    Function Func1(Number As Variant) As Variant
    	If IsNumeric(Number) Then
    		' Вычисление корректного результата.
    		Func1 = Number * Number
    	Else	'аргумент некорректен
    		Func1 = CVErr(1999) ' возвращает код ошибки
    	End If
    End Function

    Проверять корректность работы Func1 можно так.

    Sub Testfunc1()
    Dim res As Variant, arg As Variant
    	arg = 12
    	res = Func1(arg)
    	If IsError(res) Then		'проверка ошибочности результата
    		Debug.Print "Ошибка #: ", res, "аргумент : ", arg
    
    	Else
    		Debug.Print "Результат : ", res
    	End If
    	arg = "двенадцать"
    	res = Func1(arg)
    	If IsError(res) Then		'проверка ошибочности результата
    		Debug.Print "Ошибка #: ", res, "аргумент : ", arg
    
    	Else
    		Debug.Print "Результат : ", res
    	End If
    End Sub

    Приведем результаты вычислений:

    Результат :	144 
    Ошибка #:	 Error 1999	аргумент :	двенадцать

    Оптимизация программ

    Результаты работы с системой документов должны удовлетворять заданным спецификациям. Эти результаты должны быть получены в заданное время. Точный прогноз погоды на завтра требуется сегодня, получить его послезавтра становится бессмысленным. Поэтому программисту постоянно приходится заботиться не только о корректности работы своих программ, но и об их эффективности. И здесь успех, во многом, определяется решениями, принимаемыми на самом верхнем уровне разработки проекта. Самое важное решение связано с выбором подходящей среды программирования. Здесь нужно точно учитывать перспективы развития разрабатываемой системы и выбираемой среды программирования, насколько средства, выбранные Вами сегодня, смогут удовлетворять Вашим завтрашним потребностям. Другой разрез, для многих задач не менее важный, состоит в правильном выборе структур данных и алгоритмов их обработки. Нет ничего лучше хорошего алгоритма. Он, как правило, определяет эффективность решения задач. Важную роль в повышении эффективности играет и переиспользование. Ранее созданные, тщательно проверенные и эффективные компоненты, могут определить и эффективность вновь решаемой задачи. Рассмотрение всех этих вопросов, главных источников эффективности работы системы в целом, выходит за рамки данной книги. Мы остановимся сейчас на деталях, каждая из которых в отдельности не способна существенно повысить эффективность решения. При добыче золота, можно пытаться найти самородок, а можно промывать руду, добывая песчинку за песчинкой. Так и при разработке программ, песчинки, повышающие эффективность, могут принести в результате ощутимый эффект.

    Прежде, чем начать рассмотрение отдельных элементов, еще пару слов о измерениях, которые выполняет программист, для повышения эффективности своей программы. Известен эффект "bottle neck", - узких мест программы, которые могут быть причиной ее не эффективного поведения. Чтобы получить представление о том, есть ли в программе такие узкие места, требующие первостепенного вмешательства и переписывания, чаще всего требуется построение полного профиля программы. Причем и здесь важна представительная система тестов. Опять таки, мы не будем сейчас рассматривать средства построения профиля. Отметим только, что в ряде случаев можно для проведения измерений использовать функцию Timer.По ходу изложения мы несколько раз демонстрировали ее применение.

    Приемы оптимизации кода

    По ходу изложения мы не раз обращали внимание на средства, повышающие эффективность вычислений. Об этом шла речь, при рассмотрении модулей, при рассмотрении вопроса об объявлении переменных и выполнения операций над ними. Сейчас мы приведем еще несколько советов, некоторые из которых уже упоминались ранее.

    Объявление переменных

    Для уменьшения размеров требуемой памяти и ускорения выполнения операций над данными:

  • При работе с большими массивами данных, размер которых, как правило, может изменяться, используйте динамические массивы или динамические структуры данных.
  • Объявляйте переменные в строгом соответствии с их возможным типом. Не используйте без крайней необходимости тип Variant. Работа с Variant требует больше памяти и дополнительных преобразований.
  • При задании типа, как правило, используйте тип с более узкой областью определения, достаточной для представления возможных значений. Здесь могут быть исключения, специфические для VBA. Так переменные типа Integer преобразуются к типу Long, так что разумнее задавать сразу тип Long вместо типа Integer.
  • Операции с плавающей точкой выполняются более долго, поскольку требуют обращения к сопроцессору. Поэтому тип Single и Double следует использовать только в случае действительной необходимости. Иногда, можно воспользоваться типом Currency для выполнения подобных действий. В этом случае потребуется больше памяти, но не будет обращений к сопроцессору с плавающей точкой.
  • При работе с объектами следует вводить переменные соответствующего объектного типа. Они хранят ссылки на объекты, а работа со ссылками выполняется значительно быстрее, чем с самими объектами.
  • При объявлении объектных переменных избегайте объявления Object, указывайте явный тип объекта. Типы Object и Variant весьма полезные типы, но применять их нужно только в случае действительной необходимости.
  • В одном из примеров, я приводил таблицу временных измерений выполнения арифметических операций над данными разных типов. Рекомендую провести подобные замеры в более полном объеме.

    Математические операции

  • При работе с целыми используйте целочисленные операции, - деление нацело, взятие остатка и другие, не выводящие за пределы целых чисел.
  • В выражениях, где одновременно присутствуют целые числа и числа с плавающей точкой перед проведением операций всегда происходит преобразование к типам с плавающей точкой Single и Double. Для уменьшения подобных преобразований, записывайте выражения так, чтобы пересечение переменных целого и плавающего типа было, по возможности, минимальным.
  • Строковые операции

  • При работе со строками используйте введенные в Office 2000 функции Replace, функции разбора строки и другие. Ранее мы подробно рассказали об их достоинствах.
  • Избегайте, по возможности, использования конкатенации. Используйте Replace в большинстве случаев. В тех случаях, когда заменяется одна подстрока на другую такой же размерности, можно использовать функцию Mid, как в следующем примере:
    Public Sub TestCode()
    	Dim Text As String
    	Text = "Компилятор кода"
    	Mid(Text, 1, 5) = "Транс"
    	Debug.Print Text
    End Sub
  • Строковые константы VBA могут сократить время вычислений, позволяя избежать вызовов функций. Так, например, эффективнее использовать константу vbCrLf, чем комбинацию символов Chr(13), Chr(10), задающих возврат каретки и перевод строки.
  • Иногда медленные операции над строками можно изменить на операции работы с их кодами. Код
    If Asc(Text) = 202

    работает быстрее, чем код

    If Left(Text,1) = "K"

    Злоупотреблять такими оптимизациями не стоит, поскольку первый фрагмент менее понятен, чем второй. Он нуждается, по крайней мере, в комментариях.

  • Циклы

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

  • Все вычисления, которые могут быть сделаны вне цикла, должны быть вынесены из него.
  • Предусматривайте возможность досрочного завершения цикла, когда решение задачи уже получено.
  • Аккуратно работайте в цикле с элементами массивов. Старайтесь избегать лишнего вычисления индексных выражений и обращений к элементам массива. Часто введение дополнительных переменных позволяет существенно ускорить работу с элементами массивов.
  • При работе с коллекциями объектов Office 2000 используйте цикл For Each вместо обычного цикла For. В большинстве случаев это приводит к существенному выигрышу во времени исполнения цикла.
  • Завершая эту лекцию, хочу еще раз сказать, что главная оптимизация достигается на уровне проектных решений, выбора структуры данных и алгоритмов. К ниже перечисленным советам следует относиться с определенной осторожностью. Оптимизация не должна идти во вред ясности написания программ. Не всегда в ней есть необходимость. Но если такая необходимость возникает, то, в первую очередь, нужно выявить узкие места в программе и их, в основном, и оптимизировать.

    Вернуться к учебному плану