Глава 6. Тестирование и отладка

Как пользователь Windows вы, конечно же, сталкивались с множеством чужих ошибок в программах. Теперь пора и вам как разработчику программного обеспечения наделать массу ошибок.

Вот он, решающий момент: вы последний раз ударили по клавишам, и ваша, несомненно, выдающаяся программа, готова! Она принесет вам известность и славу, и, как маленькому ребенку, вам хочется закричать: „А теперь хвалите меня!“. Но первый восторг проходит вместе с первым запуском программы — похоже, она не делает того, чего вы хотели… Здесь явно неправильный вывод на экран, тут операционная система сообщает, что лучше бы вам заняться более спокойным и доходным, по сравнению с программированием, делом и не приводить ее в состояние недоумения… Что же делать?

Но вы — счастливчик: вы работаете с Delphi! Интегрированная среда разработки предоставляет не одну возможность упростить тестирование и отладку приложения. В этой главе вы познакомитесь с ними, так что пользователи вашей программы не будут расточать в ваш адрес не самые лестные предположения о вашей квалификации программиста.

Тестирование

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

Создание надежного приложения

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

Ваше приложение должно быть хорошо организовано. Разделите программу на модули, каждый из которых выполняет определенные задачи. Например, если код, создающий отчет, разнесен по десяти модулям, время отладки такого кода увеличится даже более чем в десять раз (хотя бы за счет поиска нужной строки в десяти модулях). Конечно же, вы можете вызывать подпрограммы из других модулей, но они должны быть созданы для выполнения четко поставленной задачи. Глупо размещать одну половину выполняемой операции в процедуре в одном модуле, а вторую половину — в другой процедуре (тем более — в другом модуле). Пусть это примитивный совет, но он является одним из самых действенных! Порядок — прежде всего! Порядок в мыслях и в программе!

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

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

Отладочная и коммерческая версии кода

Те, кто участвовали в „полевых испытаниях“ (известных как бета-бета-тестированиекоммерческих программ, наверняка обратили внимание, что такие версии программ более медлительны, гораздо более „разговорчивы“ и размером побольше окончательных версий программ. Может быть, разработчик спешил и выпустил „сырой“ продукт, который будет улучшать перед выпуском окончательного варианта? Так тоже бывает, но главная причина в другом: в бета-версии содержится тестовый и отладочный коды, используемые разработчиком для проверки корректности работы программы.

Delphi позволяет очень легко внести тестовый и отладочный коды в приложение. Например, вы хотите создать приложение работы с базой данных и использовать быстрый, но, возможно, несколько рискованный алгоритм сортировки данных. Как же убедиться в корректности его работы? Один из путей — использовать в приложении два алгоритма одновременно (быстрый, но рискованный, и медленный, но проверенный), затем сравнить результаты работы обоих алгоритмов. Конечно же, этот вариант используется только в бета-версии, и после всестороннего тестирования, если все работает отлично и без сбоев, в конечной версии продукта останется только быстрый (и после такого тестирования — уже не рискованный) метод сортировки.

Для этого вам вовсе не надо использовать два разных текста программ — воспользуйтесь возможностью условного компилирования. Вы можете определить символ (я обычно использую Debug, но вы свободны в вашем выборе) для переключения между коммерческой и отладочной версиями вашего кода с использованием директив $IFDEF, $IFNDEF, $ELSE и $ENDIF. Вот пример использования „медленного“ алгоритма в отладочной версии.

DataSet:= GetData; //Получение данных для сортировки.

{$ifdef Debug}

TestResultSet:= SortTortoise(DataSet); //Медленно и надежно.

{$endif}

ResultSet:= SortHare(DataSet); //Быстро и рискованно.

{$ifdef Debug}

if not CompareData(ResultSet, TestResultSet) then

//Результаты совпали?

Raise Exception.Create('Сортировка в DataSorting некорректна');

{$endif}

Если определен символ Debug, код принимает следующий вид.

DataSet:= GetData; //Получение данных для сортировки.

TestResultSet:= SortTortoise(DataSet); //Медленно и надежно.

ResultSet:= Sort Hare(DataSet); //Быстро и рискованно.

if not CompareData(ResultSet, TestResultSet) then

//Результаты совпали?

Raise Exception.Create('Сортировка в DataSorting некорректна');

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

DataSet:= GetData; //Получение данных для сортировки.

Re5ultSet:= SortHare(DataSet); //Быстро и рискованно.

Как видите, использование условной компиляции — простои способ создания как отладочной, так и коммерческой версий приложения Вы можете определить символ условной компиляции двумя путями. Первый — глобальное определение символа в опциях проекта. Выберите команду Project/Options и в диалоговом окне Project Options, во вкладке Directories/Conditionals, введите символ в поле Conditional defines. На рис 2.1 показано определение двух символов (Debug и Alpha) Щелкните на кнопке ОК для подтверждения вашего ввода

Совет: Изменив символы условной компиляции, перекомпилируйте проект с помощью команды Project/Build All для того, чтобы учесть внесенные изменения.

Другой метод определения символа условной компиляции — вставить в ваш исходный код директиву.

{$define Debug}

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

{$undef Debug}

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

Помимо директив условной компиляции, есть еще немало других директив, которые могут использоваться в отладочной версии приложения. Я говорю „могут“, поскольку эти директивы могут внести определенные различия в код коммерческой и тестовой версий, так что будьте осторожны при их применении. Эти опции перечислены во вкладке Compiler диалогового окна Project Options, приведенного на рис 2.2

Рис. 2.1.Использование диалогового окна Project Options для определения символов условной компиляции

Рис 2.2. Использование диалогового окна Project Options для изменения отладочных опций компилятора

Ниже приведено описание этих опций.

Optimization. Эта опция управляет оптимизацией компилятора. Рекомендуется оставить эту опцию включенной и выключать ее, если вы полагаете, что оптимизация вносит ошибки в вашу программу. Управлять оптимизацией локально вы можете с помощью директив компилятора $0+ и $0-.

Stack Frames. Если эта установка включена, компилятор всегда включает в функцию код для генерации кадра стека, даже если код не использует стек. Как и в случае оптимизации, вам вряд ли стоит изменять эту установку. Локальные директивы компилятора— $W-t и $W-.

Range Checking. Проверка диапазона перехватывает ошибки, вызванные выходом за пределы массива или строки. Однако дополнительный код сдерживает выполнение программы и, по всей видимости, вы отключите эту опцию в коммерческой версии. Директивы компилятора для включения и отключения проверки— $R+ и $R-.

Assertions (С). Эта опция более полно описана в следующем разделе. Использование данного типа проверок позволяет быстро и просто добавить проверки в код Естественно, в коммерческой версии вы захотите отключить эту возможность. Директивы компилятора— $С+ и $С-.

Overflow checking (Q). Проверка на переполнение позволяет выяснить, не является ли результат выполнения целочисленной операции слишком большим для размещения его в переменной. Подобно опции Range Checking, данная опция полезна только при отладке, и в коммерческой версии, как правило, отключается. Директивы компилятора— $Q+ и $Q-. Отладочная версия вашего кода, вероятно, будет больше по размеру и медленнее коммерческой версии. Поэтому не передайте случайно конечному пользователю отладочную версию!

Использование директивы Assert

Оператор Assert— новый оператор в Delphi 4. В действительности это просто тест на логическую истину/ложь. При использовании этого оператора вы убеждаетесь, что логическое выражение истинно, если при выполнении выражение становится ложным, генерируется исключительная ситуация. Синтаксис использования оператора таков:

Assert (<логическое выражение)

Вы можете использовать проверку, например, в начале процедуры для выяснения корректности параметров, как показано ниже.

procedure Foo(Count: Cardinal);

begin

Assert(Count < SizeOf(Word));

end;

Если выражение окажется ложным, появится сообщение об ошибке, подобное показанному на рис. 2.3. Конечно же, у вас уже вертится на языке вопрос, чем же это отличается от конструкции if… else. Дело в том, что управлять генерацией кода для оператора Assert очень легко и просто с помощью директивы компилятора. Для применения описанных возможностей используйте директиву $ASSERTIONS ON или $С +, а для отключения действия Assert— $ASSERTIONS OFF или $С — (при этом компилятор игнорирует операторы Assert и код для них не генерируется).


Рис. 2.3.Сообщение об ошибке Assert

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

{$ifdef Debug}

($ASSERTIONS ON)

{$else}

($ASSERTIONS OFF)

{$endif}

Какого типа выражения могут использоваться в операторе Assert? Любого (конечно, если оно возвращает логическое значение). Однако тут есть свои маленькие подводные камушки, о которые легко поцарапаться. Представьте себе, что у вас есть некоторая функция, например выясняющая, сколько раз она была вызвана.

function CountMe: Integer;

const ReferenceCount: Integer = 0;

begin

Inc(ReferenceCount);

Result:= ReferenceCount;

end;

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

Модульное тестирование

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

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

Рис. 2.4.О чем думают пользователи, когда ваша программа выводит сообщение об ошибке?..

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

Интегрированный отладчик Delphi

Давным-давно, когда компьютеры были большими, а именно — лет двадцать назад, отладка заключалась в копании в огромных кипах бумаги с исходными текстами и распечатках результатов работы и потреблении невероятного количества кофеина, а зачастую и анальгина… Современный программист, особенно если он программирует на Delphi, больше не чернорабочий от программирования с крепкими от переворачивания центнеров бумаги мускулами, а „белый воротничок“, окруженный разнообразными ''электронными мухобойками», предоставляемыми Delphi.

Интегрированный отладчик Delphi переполнен полезными, не очень полезными и совсем бесполезными возможностями, и кажется, что изучить их просто невозможно. Однако использование отладчика Delphi просто, как апельсин: ведь когда вы компилируете свою программу и запускаете ее из среды Delphi, вы уже пользуетесь встроенным отладчиком, хотя, вероятно, и не подозреваете об этом. Как всегда, 90 % проблем решается 10 % возможностей программного обеспечения, а потому все разнообразие работы с отладчиком вы можете изучить позднее, при решении оставшихся 10 % ваших проблем.

Настройка IDE для отладки

Для работы со встроенным отладчиком Delphi 4 его интегрированная среда разработки (IDE) предлагает целую серию установок, большинство из которых вам лучше не трогать, а оставить, как есть (по умолчанию). Однако если вы все-таки решили изменить установки, выберите команду Tools/Options и в появившемся диалоговом окне Environment Options щелкните на вкладке Preferences (она показана на рис 2.5)

Ниже перечислены опции вкладки Preferences и их функции.

Integrated Debugging. Позволяет включать и отключать встроенный отладчик. Если вы отключите отладчик, отладочные команды в меню Run станут недоступными.

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

Рис. 2.5.Использование вкладки Preferences для настройки интегрированного отладчика Delphi.


Step Program Block. Эта опция определяет, должен ли отладчик останавливаться перед началом выполнения основного блока begin. . end при трассировке программы. Обычно данная опция отключена, и включать ее имеет смысл при добавлении кода в основной блок программы либо при отладке консольного приложения.

Hide Designers on Run. Когда эта опция включена, окно Object Inspector и формы, использующиеся при разработке приложения, перед запуском программы на выполнение закрываются. Отключение опции позволяет запускать программу быстрее, но эффект перекрывается используемыми незакрытыми ресурсами приложения. Впрочем, будет ли выбрана эта опция, зависит от пользователя.

Break on Exception. При включенной опции IDE всегда перехватывает исключительные ситуации и выводит окно сообщения, даже если в программе исключительная ситуация обрабатывается блоком try. .except. Включение этой опции упростит отладку, так как выводимые сообщения при этом будут более информативными, чем сообщения обработчика, установленные по умолчанию (сравните рис. 2.6 и 2.7). Помимо этого, IDE размещает окно редактора поверх остальных и выделяет строку, вызвавшую исключительную ситуацию.

Рис. 2.6. Сообщение об исключительной ситуации при включенной опции Break on Exception

Рис. 2.7.Сообщение об исключительной ситуации, выводимое обработчиком по умолчанию

Совет: Конечно, опция break on Exception полезна, но может привести в растерянность новичка в Delphi, особенно когда сообщается об исключительной ситуации, которую должен обработать блок try. .except. Вы можете либо отключить эту опцию, либо запустить приложение не из среды Delphi, чтобы увидеть его работу глазами конечного пользователя.

Minimize on Run Опция сворачивает окно IDE при запуске приложения Подобно опции Hide Designers on Run, ее установка зависит исключительно от личных предпочтений программиста На странице Display диалогового окна Environment Options есть еще одна установка — опция Visible Gutter. Она включает или отключает отображение серой вертикальной полосы, расположенной слева от окна редактирования (рис. 2.8), на которой мнемоническими значками отображается отладочная информация

Включение в код отладочной информации

Перед началом отладки следует убедиться, что в приложение включена отладочная информация Delphi.

Для компиляции проекта с отладочной информацией следует выполнить команду Project/Options и в диалоговом окне Project Options выбрать вкладку Compiler (рис. 2.9).


Рис. 2.8.Окно редактора с отладочными значками

Рис. 2.9 Вкладка compiler диалогового окна Project Options

Включение отладочной информации регулируется следующими установками:

Debug Information. Опция контролирует включение отладочной информации. При отключении этой опции вы не сможете трассировать код или ставить точки прерывания в любом модуле. Опция эквивалентна директивам компилятора $D и $DEBUGINFO

Local Symbols. Опция контролирует включение информации о локальных переменных, декларированных, например, внутри функций, процедур и раздела implementation. Вряд ли у вас возникнет необходимость в отключении этой опции, тем более что она игнорируется при выключенной предыдущей опции. Эквивалентные директивы компилятора— $L и $LOCALSYMBOLS.

Symbol Info. Эту опцию нельзя целиком отнести к разряду отладочных, так как ее действие направлено на броузер объектов, а не на встроенный отладчик. Если опция включена, броузер объектов сможет выводить информацию для объектов, определенных в модулях Опция игнорируется при выключенных предыдущих двух опциях Эквивалентные директивы компилятора — $Y и $REFERENCEINFO Обычно вы будете включать опции Debug Information и Local Symbols для пошаговой трассировки приложения. Однако, как упоминалось ранее, вы можете отключить отладочную информацию для некоторых модулей (просто используйте соответствующую директиву в начале модуля).

unit MyUnit;

{$D-}

interface

Использование директивы $D— автоматически отключает опции Local Symbols и Symbol Info, так что вам не надо отключать их отдельно.

Совет: Если вы распространяете модули Delphi в виде DCU-файлов (например, VCL) и не распространяете их исходных текстов позаботьтесь о том, чтобы в скомпилированных модулях не содержалась отладочная информация

Пошаговая отладка

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

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

Интегрированная среда Delphi предоставляет пользователю несколько команд пошаговой отладки доступных в меню Run (рис 2.10)

Рис 2.10 Используйте меню Run для выполнения команд отладки

Ниже перечислены команды отладки.

Run. Выбор этой команды запускает приложение на выполнение в обычном режиме. Вы можете использовать ее как для запуска приложения, так и для продолжения его работы после какого-либо прерывания выполнения (например, по точке останова). Если включена опция Break on Exception, используйте команду для продолжения работы после получения сообщения об исключительной ситуации

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

Trace Into. В отличие от предыдущей команды, эта опция отработает пошаговую отладку вызываемых процедур и функций. Другими словами, если, например, в строке вызывается некая процедура, то при выполнении этой команды точка выполнения перейдет на первую строку процедуры. Однако если в строке нет таких вызовов, значит, последние две команды идентичны. Будьте осторожны при пошаговой трассировке обработчика события OnPaint. Поскольку при пошаговой отладке окно редактора размещается поверх других окон, требуется перерисовка окна приложения, для чего вызывается обработчик события OnPaint… Вы попадаете в замкнутый круг, точнее — в бесконечный цикл вызовов одного и того же обработчика. Тем не менее, стоит лишь проследить, чтобы окна приложения и редактора не перекрывались, и проблема разрешится сама собой.

Trace to Next Source Line. Иногда ваш код вызывает другой код косвенно, например, при вызове функции, которая запускает обработчик события, или при вызове функции Windows API, которая, в свою очередь, запускает функцию косвенного вызова. Поскольку такие вызовы косвенные, отладчик не видит вызова и не отслеживает пошагового выполнения таких вызовов. Однако использование описываемой команды приводит к отслеживанию таких вызовов и останову отладчика на первой строке вызываемой таким образом функции или процедуры.

I Run to Cursor. Зачастую вам вовсе не хочется в поисках ошибки, местоположение которой с какой-то точностью вам известно, пошагово добираться до нужного места через сотни, а то и тысячи строк кода. В таком случае просто поместите курсор на нужную вам строку программы в окне редактирования и используйте команду Run to Cursor. Эти действия эквивалентны временному помещению точки останова в необходимую вам строку программы, и после выполнения предшествующего строке кода работа программы приостанавливается. Если вы пытаетесь выполнить программу до позиции курсора, который находится в строке, не содержащей отладочной информации, вы получите сообщение об ошибке, показанное на рис. 2.11.

Рис. 2.11.Это сообщение о том, что вы пытаетесь остановить выполнение программа на строке, не содержащей отладочной информации.

Show Execution Point. Эта команда заставляет среду разработки открыть окно редактора и показать выполняемую в настоящее время строку программы. Она полезна в случаях, когда вы, например, закрыли или свернули окно редактора во время отладки (обычно при нормальном состоянии окна отладчик делает это автоматически).

Program Pause. Выбор этой команды немедленно останавливает выполнение программы. Она особенно полезна при зацикливании программы.

Program Reset. Если вы достаточно «наотлаживались» и хотите завершить работу своей программы или запустить ее заново, используйте эту команду. Она немедленно прекратит выполнение программы и вернет вас в среду разработчика. Многие команды имеют связанные с ними комбинации клавиш, например <F9 для Run. Однако назначения клавиш могут быть изменены во вкладке Editor диалогового окна Options. Например, команде Step Over назначена клавиша <F8, но при выборе назначений клавиш в стиле редактора BRIEF назначенной комбинацией клавиш становится <Ctrl+F11. Вы можете увидеть назначенные клавиши непосредственно в меню Run, как показано на рис. 2.10. Помимо этого, на панели инструментов есть кнопки, вызывающие некоторые из этих команд (вы имеете возможность также удалить некоторые из них или добавить новые).

Просмотр значений переменных

При пошаговом прохождении программы в отладчике вы, несомненно, захотите узнать, что содержится в различных переменных. Для этого можете использовать окно просмотра переменных Watch List, которое предоставляет возможность пассивно просматривать содержимое одной или нескольких переменных, или диалоговое окно Evaluate/Modify, позволяющее работать только с одной переменной (в нем можно не только просмотреть, но и изменить ее содержимое).

Для просмотра значения переменной используйте команду Run/AddWatch или установите указатель мыши на переменную в окне редактирования, щелкните правой кнопкой мыши и выберите из контекстного меню команду Add Watch at Cursor. После этого появится диалоговое окно Watch Properties, показанное на рис. 2.12. Введите имя переменной в поле Expression (если оно не появилось там автоматически). Обратите внимание на то, что вы можете просматривать значения не только переменных, но и выражений типа х*(y+z). Единственное ограничение — выражение не может содержать вызовов функций, поскольку это может вызвать побочный эффект, связанный с незапланированным вызовом функции, описанный выше, в подразделе ''Использование директивы Assert". Допускается также просмотр значений записей, массивов и других структурированных элементов.

Поле Repeat Count используется в том случае, если у вас есть большой массив, и вы хотите просмотреть его часть. Предположим, что вам надо знать значения элементов 826–833 следующего массива

var

BigArray: array[1..1000] of Integer;

Вы не можете просмотреть массив BigArray, так как 1 000 элементов просто не поместятся в окне (да и пролистать ненужные вам 825 элементов — работенка немалая!) Вместо этого вы просите показать вам значение BigArray [826] и устанавливаете параметр Repeat Count равным 8. При этом вам будут показаны значения восьми элементов массива — от 826 по 833.

Использование поля Digits позволяет определить количество значащих цифр при выводе числа с плавающей точкой.

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

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

На рис 2.13 отображены значения нескольких переменных в окне Watch List. Кроме того, в некоторых случаях отладчик выводит информацию о причинах, по которым содержимое переменной недоступно.


Рис. 2.12.Использование диалогового окна Watch Properties для добавления или изменения набора переменных в окне просмотра.

Рис. 2.13.Просмотр переменных.

Смысл приведенных сообщений поясняется ниже

Variable 'X' inaccessible here due to optimization(Переменная 'X' недоступна из-за оптимизации). В этой точке значение переменной просмотреть невозможно (иногда это можно сделать в другом месте программы), так как для нее не выделена память из-за оптимизации программы компилятором.

Symbol was eliminated by linker (Переменная удалена компоновщиком). Переменная удалена из кода программы компоновщиком, так как на нее нет ни одной ссылки в тексте программы. Отладчик также выводит сообщение об ошибке, если имя переменной написано неправильно, например вы запросили значение Foo [5], в то время как переменная Foo массивом не является.

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

Диалоговое окно Evaluate/Modify

Для вывода диалогового окна Evaluate/Modify выберите команду Run/Evaluate/Modify (рис 2.14). Другой способ вызова диалогового окна — установить курсор в окне редактирования на необходимой вам переменной, щелкнуть правой кнопкой мыши и в контекстном меню выбрать команду Evaluate/Modify.

Введите выражение в поле Expression так же, как в диалоговом окне Watch Properties (см рис 2.12). Затем щелкните на кнопке Evaluate для того, чтобы увидеть результат в поле Result. Если выражение состоит только из имени простой переменной (не массива или структуры!), можете ввести новое значение для переменной в поле New Value и, щелкнув на кнопке Modify, присвоить переменной новое значение. Это позволяет корректировать значения переменных в процессе отладки, не останавливаясь и не перекомпилируя всю программу, что сбережет ваше время и нервы, и даст возможность, найдя одну ошибку, искать другие, а исправлением первой ошибки заняться чуть позже.


Рис 2.14. Использование диалогового окна Evaluate/Modify для проверки изменения отдельной переменной.

Диалоговое окно Evaluate/Modify — немодальное, т. е. вы можете не закрывать его, продолжая отладку. Однако учтите, что в отличие от окна Watch List диалоговое окно Evaluate/Modify не отслеживает изменения значений переменных и для получения информации о текущем состоянии переменной вам необходимо воспользоваться кнопкой Evaluate.

Так же, как и окно Watch List, диалоговое окно Evaluate/Modify может выводить сообщения об ошибках, если отладчик не в состоянии вывести информацию о переменных. И точно так же не выводится информация о выражениях, содержащих вызов функций.

Как видите, окно Watch List и диалоговое окно Evaluate/Modify очень похожи, но каждое из них имеет свои преимущества в различных ситуациях. Обратите внимание на одно существенное отличие диалогового окна Evaluate/Modify его вывод выполняется в поле с несколькими строками, а потому оно более удобно для просмотра структур и объектов.

Установка точек останова

Точка останова (breakpoint) — своеобразный знак STOP для отладчика (на полосе слева в окне редактора она и выглядит как маленький красный значок). Когда ваше приложение запущено под отладчиком и доходит до строки, в которой находится точка останова, оно прекращает работу и ждет ваших дальнейших распоряжений. Такие точки могут быть условными и безусловными. Отладчик всегда останавливается на точке безусловного останова и может останавливаться в точке условного останова, когда выполнено условие. Интегрированный отладчик Delphi поддерживает два типа условий — логическое и по количеству проходов. Ниже рассмотрены оба типа.

Установить точки останова можно следующими способами:

Поместите курсор редактирования на выбранную строку программы и нажмите клавишу команды Toggle Breakpoint (по умолчанию это клавиша <F5>) для установки или удаления точки останова в этой строке. То же самое можно выполнить и с помощью контекстного меню.

Выберите команду Run/Add Breakpoint, и откроется диалоговое окно Edit breakpoint (рис 2.15). Для установки простейшей точки останова просто щелкните на кнопке New. Вы также можете использовать поля Filename и Line Number для установки точек останова в другом файле или строке за пределами текущей позиции курсора. Поля Condition и Pass count используются для установки точки условного останова. После установки одной или нескольких точек останова можете использовать окно Breakpoint List для управления ими. Для вызова окна Breakpoint List выберите команду View/Breakpoints (рис. 2.16). В этом окне можете щелкнуть на строке конкретной точки правой кнопкой мыши и в контекстном меню отключить точку останова с помощью команды Disable (вновь включить точку останова можно с помощью команды Enable) или удалить ее с помощью команды Delete. Команды View Source и Edit Source активизируют окно с текущим файлом исходного текста, при этом команда Edit Source устанавливает курсор в строку с точкой останова. Команда Properties выводит диалоговое окно Edit breakpoint, показанное на рис. 2.15, позволяя тем самым изменять параметры точки останова.

Рис. 2.15. Использование диалогового окна Edit breakpoint для установки новой точки останова.

Рис. 2.16. Использование окна Breakpoint list для управления точками останова.

После щелчка правой кнопкой мыши в окне при не выбранной точке останова выводится контекстное меню, в котором команда Add служит для добавления новой точки, Delete All удаляет все точки останова, а команды Disable All и Enable All отключают или включают все точки останова в списке.

Для превращения безусловной точки останова в условную, необходимо вызвать диалоговое окно Edit breakpoint (см. рис. 2.15) и ввести условное выражение или количество проходов в соответствующие поля.

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

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

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

Отладка DLL

В предыдущих версиях Delphi для отладки библиотек динамической компоновки требовался внешний отладчик (Turbo Debugger for Windows). Delphi 4 внесла возможность отладки DLL в список своих возможностей. Windows не может загрузить DLL без предварительной загрузки использующего ее ЕХЕ, поэтому вам с начало придется набросать простенькую программку, использующую интересующую вас DLL. В главном меню выберите команду Run/Parameters для вывода диалогового окна Run Parameters. Если текущий проект— DLL (DPR-файл начинается ключевым словом library, а не program), поле Host Application будет доступно, и в нем вам надо либо ввести имя использующей DLL программы, либо выбрать его с помощью кнопки Browse.

После выбора приложения запуск и отладка DLL становятся обычной рутинной работой со всеми возможностями, используемыми при отладке программ, — установкой точек останова, просмотром значений переменных и т. д.

Точно так же вы будете отлаживать и свои компоненты ActiveX, и объекты автоматизации OLE.

Окно CPU (дизассемблер)

Окно CPU предоставляет возможность увидеть работу приложения на уровне языка ассемблера. Те, кто использовали отладчик Turbo Debugger for Windows, должны знать возможности, предоставляемые таким окном. Эффективное его использование предполагает знание ассемблера Intel x86 и архитектуры процессора, так что, если вы не вполне уверенно себя чувствуете, можете пропустить этот раздел.

Окно CPU требуется крайне редко, но если в нем возникла необходимость, значит, это действительно необходимость. Это — ultima ratio, последний довод, и используется он в безвыходных положениях, когда обычная трассировка кода не позволяет найти, понять и исправить ошибки. Только в таких случаях окно CPU и проход по ассемблерным инструкциям может приподнять завесу над причиной возникновения ошибок.

Чтобы использовать окно CPU необходимо его включить (по умолчанию оно отключено). Для этого придется использовать программу RegEdit из поставки Windows 95 (Windows NT). Запустите RegEdit и пробирайтесь по иерархическому дереву папок. Сначала откройте папку HKEYCURRENTUSER. Во вложенной ветви зайдите в папку Software, затем — в Borland, Delphi и, наконец, в 4.

Одна из папок называется Debugging. Щелкните на ней и, когда она откроется, появится список пар Имя/Данные в окошке справа. Добавьте новое значение с именем EnableCPU и значением 1.

При следующем запуске Delphi вы увидите новый подпункт меню View/CPU Window.

Для вывода окна выберите View/CPU Window.

Перечислим панели

Code pane Панель кода представляет дизассемблированный код в окрестности текущей точки выполнения (если вы не отлаживаете приложение, окно будет полупустым). Кроме того, панель показывает исходный текст строк, соответствующих выполняемому коду. В окне редактирования точка выполнения индицируется маленьким зеленым значком. При пошаговом проходе значок точки выполнения синхронно перемещается по окну CPU и окну редактирования.

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

Flags pane. Панель флагов показывает состояние 14 флагов процессора. Установленный флаг представляется значением 1, сброшенный флаг значением 0. В зависимости от процессора некоторые флаги могут быть недоступными.

Stack pane. Панель стека показывает содержимое стека приложения. Вы можете изменять представление содержимого стека с помощью контекстного меню.

Data pane. По умолчанию в панели данных выводится содержимое глобального сегмента данных приложения. Ее вид можно изменить так же, как и вид панели стека. Каждая из панелей в окне CPU имеет собственное контекстное меню. Вы можете поэкспериментировать с пунктами меню и получить более полное представление о возможностях манипулирования панелями.

Окно состояния подзадач.

Окна Thread Status, Modules и Call Stack предоставляют дополнительную информацию, которая может быть полезна при отладке приложения.

В окне Thread Status перечислены все активные подзадачи текущего процесса. Для просмотра состояния подзадач выберите команду View/Threads, и на экране появится окно Thread Status (рис 2.17).

Рис 2.17.Использование окна Thread Status для просмотра атрибутов подзадач в приложении

В четырех колонках окна представлена следующая информация:

Thread ID. Уникальный идентификатор подзадачи, присвоенный ей операционной системой.

State. Состояние подзадачи, обычно — Running или Stopped. Если ваше приложение запущено, но ожидает ввода от пользователя, состояние выводится как Runnable.

Status. Статус подзадачи может иметь одно из четырех значений. Breakpoint означает, что поток остановлен в точке останова. Stepped— подзадача находится в режиме пошагового выполнения. Faulted— остановка подзадачи из-за исключительной ситуации и Unknown — статус неизвестен.

Location. В этой колонке выводится строка исходного кода, соответствующего текущей точке выполнения подзадачи. Если отладчик не в состоянии определить строку исходного текста, выводится 32-битовый адрес точки выполнения. Если вами разработано приложение с несколькими подзадачами, и вы хотите отладить одну из подзадач, можете сделать ее основной с помощью окна Thread Status. Выберите подзадачу, которою вы хотите сделать текущей, и щелкните на ней правой кнопкой мыши. Выберите из контекстного меню команду Make Current. При этом фокус выполнения будет передан выбранной подзадаче, и вы сможете отлаживать ее как основную задачу.

В контекстном меню окна содержатся две команды — View Source и Go to Source. Они могут пригодиться для того, чтобы проследить за точкой выполнения другой подзадачи без передачи ей фокуса.

Окно Modules

В окне Modules отображаются все модели (ЕХЕ-файл вашего приложения и все используемые динамические библиотеки), которые располагаются в адресном пространстве приложения. В него входят непосредственно подключенные DLL и библиотеки, подключенные через другие библиотеки, а также библиотеки, загруженные операционной системой. Чтобы увидеть это окно, изображенное на рис 2.18. выберите команду View/Modules. В окне информация выводится в трех столбцах Name (имя модуля), Address (адрес начала кода модуля) и Path (полный путь каталога, из которого был загружен модуль). Информация о каталоге может быть важна, если возможна загрузка модуля не из того каталога, из которого ожидалась, например, в более старой версии. Информация об адресе обычно используется при отладке в окне CPU.

Окно Call Stack

В этом окне представлен список всех функций и процедур, вызванных к моменту достижения точки выполнения и работа которых приостановлена. Для открытия этого окна, показанного на рис 2.19, используйте команду View/Call Stack.

Рис. 2.18.Использование окна Modules для вывода списка модулей, используемых приложением.

Рис. 2.19 Использование окна Call Stack для определения всех вызванных функций и процедур.

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

Трассировка исходного кода VCL

Если вы используете соответствующую версию поставки Delphi 4, значит, в нее входят исходные тексты VCL (Visi Component Library). В поставку VCL входят скомпилированными без отладочной информации, что означает, что при отладке вы не сможете пройти код пошагово. Нет особой необходимости трассировать код VCL, но если вы хотите убедиться, что ошибка не в VCL, или посмотреть, как работает функция, придется перекомпилировать модули, которые нужно трассировать, с отладочной информацией.

Совет: Некоторые стандартные модули VCL требуют компиляции с отключенной опцией Overflow Checking для корректной работы. Поэтому при перекомпиляции убедитесь, что эта опция компилятора отключена.

Силовая отладка

Под силовой отладкой (brute-force debugging), отладкой «в лоб», понимаются методы отладки, основанные не на возможностях отладчиков, а на трюках, родословная которых, пожалуй, восходит к временам Атанасова и Лебедева, создававших первые ЭВМ по обе стороны океана.

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

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

ПРЕДОСТЕРЕЖЕНИЕ: Большинство таких методов — из серии «быстро и грязно», и я бы не рекомендовал заменять ими описанные ранее методы тестирования и отладки.

Вывод отладочной информации в форме.

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

Посмотрите на описания функций ExtractFileDir и ExtractFilePath в справочной системе Delphi 4. Я не берусь точно судить по документации о различии между этими функциями, но я знаю, что мне делать. Я создаю новое приложение (выбрав пункт меню File/New Application) и помещаю в главную форму элемент TButton и два элемента TLabel (форма будет выглядеть так, как на рис. 2.20).

Дважды щелкните на кнопке TButton и добавьте код к обработчику события OnClick.

procedure TFormI.ButtonlClick(Sender: TObject);

begin

Labell.Caption:= ExtractFileDir(Application.ExeName);

Label2.Caption:= ExtractFilePath(Application.ExeName);

end;

(Application. ExeName возвращает полное имя файла приложения). Нажмите клавишу <F9> для компиляции и запуска приложения и щелкните на кнопке. Теперь вам должно быть ясно, чем различаются эти две функции.

Недавно у меня возникла проблема с чужой DLL, исходного кода которой я, естественно, не имел. Странным было то, что эта DLL уцелела при загрузке и не освобождала большой фрагмент виртуальной памяти. Я создал маленькое приложение, в котором после каждого щелчка на кнопке сообщалось, сколько виртуальной памяти свободно. Мне хотелось сохранять предыдущие результаты, а потому, я использовал элемент управления TMemo и добавлял в него новые строки с результатами.

Чтобы посмотреть, как это делается, создадим новое приложение и разместим в форме элементы управления TMemo и TButton (и не забудем установить значение свойства TMemo.ScrollBars равным ssVertical). Ваша форма будет выглядеть так, как на рис. 2.21.

Рис. 2.20.Вывод отладочной информации с использованием элемента управления TLabel.

Рис. 2.21. Вывод отладочной информации в элемент TMemo

В обработчик события onclick добавьте следующий код.

procedure TFormI.ButtonlClick(Sender: TObject);

var

MemStat: TMemoryStatus;

begin

VirtualAlloc(nil, 1000000, MEMRESERVE, PAGEREADWRITE);// 1

MemStat.dwLength:= SizeOf(TMemoryStatus); // 2

GlobalMemoryStatus(MemStat); // 3

Memol.Lines.Add(IntToStr(MemStat.dwAvailVirtual)); // 4

end;

Не беспокойтесь о деталях вызова API-функции VirtualAlloc в строке 1. Здесь ее вызов требует от операционной системы зарезервировать миллион байтов памяти для дальнейшего использования. API-функция GlobalMemoryStatus возвращает информацию об использовании памяти приложением и системой в целом. Информация возвращается в переменной MemStat, представляющей собой запись типа TMemoryStatus. Перед вызовом GlobalMemoryStatus вы передаете системе информацию о размере структуры, как в строке 2, а затем вызываете функцию (строка 3) и выводите информацию в TMemo в строке 4.

Скомпилируйте и запустите программу, щелкните несколько раз на кнопке — и увидите, что виртуальная память уменьшается примерно на один мегабайт при каждом щелчке, как и ожидалось. На рис. 2.23 показана форма после нескольких щелчков на кнопке.

Используя этот метод (без вызова VirtualAlloc), я выяснил, что на самом деле DLL затребовала около 60 Мбайт (!) виртуальной памяти при загрузке и не освободила ее. Даже притом, что Windows 95 предоставляет каждому приложению двухгигабайтовое адресное пространство, потерю 60 Мбайт сложно проигнорировать…

Рис. 2.22.Вывод в элемент TMemo информации о количестве доступной виртуальной памяти.

ShowMessage

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

Процедура ShowMessage (из модуля Dialogs) идеально подходит для этой цели Она позволяет вывести строку любой длины в простом модальном диалоговом окне. Вам только следует создать строку для вывода и передать ее процедуре (можно также использовать MessageDIg, но в нем слишком много шашечек и бантиков, которые требуют немалых усилий для достижения того же эффекта).

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

Рассмотрим простой пример. Используем этот метод для вывода информации, получаемой от уже использовавшейся функции GlobalMemoryStatus.

Создадим новое приложение и поместим TButton в основную форму. Обработчик события OnClick будет выглядеть следующим образом.

procedure TFormI.ButtonlClick(Sender: TObject);

var MemStat: TMemoryStatus;

begin

MemStat.dwLength:= SizeOf(TMemoryStatus);

GlobalMemoryStatus(MemStat);

with MemStat do ShowMessage(Format('Memory load: %d%%'#13 +

'Total physical: %d'#13+'Available physical: %d'#13 +

'Total page file: %d'#13 + 'Available page file: %d'ftl3 +

'Total virtual: %d'#13 + 'Available virtual: %d',

[dwMemoryLoad, dwTotalPhys, dwAvailPhys, dwTotalPageFile,

dwAvailPageFile, dwTotalVirtual, dwAvailVirtual]));

end;

Заметьте, что я внес в строку несколько символов #13 (ASCII-символ возврата каретки). Это позволяет разбить строку при выводе на несколько строк, что существенно облегчает чтение информации. На рис 2.23 показано, что получится после запуска программы и щелчка на кнопке.

Судя по результатам Memory load и Available physical, представленным на рисунке, мне стоит всерьез подумать о наращивании памяти своего компьютера.


Рис 2.23. Использование функции ShowMessage для вывода отладочной информации.

Вывод на консоль

Еще один способ вывода отладочной информации — вывод на консоль с использованием процедур Write и WriteLn. Вы можете конвертировать проект в консольное приложение, например, выбрав соответствующую опцию (команду Project/Options, вкладку Linker и опцию Generate Console Application) или поместив директиву $APPTYPE CONSOLE в главный DPR-файл. Учитывая, что ваше приложение — не консольное, воспользуйтесь возможностями условной компиляции и используйте директиву $APPTYPE как показано ниже:

{$ifdef Debug}

{$APPTYPE CONSOLE}

{$endif}

Теперь вывод на консоль будет осуществляться только в отладочной версии вашего приложения.

Если вы попытались использовать функцию Write или WriteLn и получили сообщение об ошибке I/O Еггог, значит, вы забыли сделать проект консольным приложением.

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

procedure TFormI.ButtonlClick(Sender: T0bject);

var MemStat: TMemoryStatus;

begin

MemStat.dwLength:= SizeOf(TMemoryStatus);

GlobalMemoryStatus(MemStat);

with MemStat do

begin

WriteLn(Format('Memory load: %d%%',[dwMemoryLoad]));

WriteLn(Format('Total physical: %d',[dwTotalPhys]));

WriteLn(Format('Available physical: %d',[dwAvailPhys]));

WriteLn(Format('Total page file: %d',[dwTotalPageFile]));

WriteLn(Format('Available page file: %d',[dwAvailPageFile]));

WriteLn(Format('Total virtual: %d',[dwTotalVirtual]));

WriteLn(Format('Available virtual: %d',[dwAvailVirtual]));

end;

end;

Результат показан на рис. 2.24.

Рис. 2.24. Использование консоли для вывода отладочной информации.

Опытные пользователи Pascal заметят, что функция Format использовалась там, где это не было необходимо (WriteLn имеет свои возможности форматирования). Однако я везде использую Format как мощный инструмент; кроме того, используя везде одну лишь функцию Format, я избавляюсь от необходимости помнить два набора правил форматирования.

Запись в Log-файл

Запись отладочной информации в файл протокола (Log-файл) существенно отличается от предыдущих приемов записи, так как это уже нельзя назвать «быстро и грязно». Это отличная технология, которую можно использовать в любом приложении.

Запись в файл протокола выполняется так же, как и вывод на консоль, но вместо WriteLn (. .) используется WriteLn (LogFile, . ), где LogFile — имя файловой переменной типа TextFile. Надо также не забывать открывать этот файл в начале работы приложения и закрывать — в конце. Проще всего этого добиться, поместив соответствующий код в свой модуль, который благодаря возможности условной компиляции подключается только в отладочной версии вашей программы.

Листинг 2.1. Модуль протоколирования отладочной информации.

unit uLoq;

interface

procedure Log(S: Strings-implementation uses

Windows, SysUtils;

var

LogFile: TextFile;

LogCriticalSection: TRtlCriticalSection;

procedure Log(S: String);

var

SystemTime: TSystemTime;

FileTime: TFileTime;

begin

GetSystemTime (SystemTime);

SystemTimeToFileTime(SystemTime, FileTime);

EnterCriticalSection(LogCriticalSection);

WriteLn(LogFile, Format('%s %.8x%.8x %5',

[FormatDateTime('yy.mm.dd hh.inm.ss'. Now),

FileTime.dwHighDateTime, FileTime.dwLowDateTime, S]));

LeaveCriticalSection(LogCriticalSection);

end;

procedure Startup;

var

FileName: String;

begin

InitializeCriticalSection(LogCriticalSection);

FileName:= Format(«Log file for %s at %s.txf,

[ParamStr(O), DateTimeToStr(Now)]);

while Pos(':', FileName) 0 do

FileName[Pos(':', FileName)]:= '.';

while Pos('/', FileName) 0 do

FileName[Pos('/', FileName)]:= '-';

while Pos('\', FileName) 0 do

FileName[Pos('\', FileName)]:= '.';

AssignFile(LogFile, FileName);

Rewrite(LogFile);

end;

procedure Shutdown;

begin

CloseFile(LogFile);

DeleteCriticalSection(LogCriticalSection);

end;

initialization Startup;

finalization Shutdown;

end.

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

unit MyUnit;

interface

uses

($ifdef Debug} uLog, {$endif)

Windows, Messages, SysUtils, Classes,

. .

Затем используйте его приблизительно так.

{$ifdef Debug)

Log(Format('Entering the Foo procedure; Bar = %d',[Bar]));

{$endif}

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

Модуль uLog обладает двумя интересными и полезными свойствами. Во-первых, каждая запись в файл предваряется информацией о дате, времени и шестнадцатеричным числом, соответствующим системному времени в миллисекундах. Эта информация может быть весьма полезной, особенно когда вы хотите отследить последовательность событий в приложении. Во-вторых, модуль использует критические разделы (critical section), что обеспечивает доступ к файлу только одной подзадачи в один момент времени.

На рис. 2.25 показан типичный файл протокола в программе Notepad.

Рис. 2.25.Пример отладочного файла протокола

Как правильно использовать файл протокола? Какую информацию в него записывать? Сколько программистов, столько и ответов на эти вопросы. Лично я предпочитаю придерживаться золотой середины между „записывай все“ и „записывай только то, что отлаживаешь“.

Обработка ошибок.

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

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

Учитесь у великих! Правило „Разделяй и властвуй“ еще никто не отменял. А потому разделяйте свою задачу на части и властвуйте. Найти ошибку или убедиться в ее отсутствии в части программы проще, чем во всей программе в целом, особенно когда появляется эффект интерференции ошибок, при котором ошибка начинает взаимодействовать с другой так, что отследить их становится очень сложно.

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

Error Setting Debug Exception Hook

Если вы увидели сообщение, показанное рис. 2.26, значит, отладчик оказался в трудном и, главное, нестабильном положении, что обычно происходит после аварийной остановки отлаживаемого приложения. Что делать? Попробуйте воспользоваться командой Run/Program Reset и запустить приложение еще раз. Не помогло? Выполните команду Program/Build All. И это не дает результата? Тогда вам придется выйти из среды разработки и запустить ее еще раз. Самый последний совет — проделать то же и с операционной системой…

Рис. 2.26. Сообщение Error setting debug exception hook

Access Violation

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

В действительности access violation — всего лишь простая ошибка, означающая, что ваше приложение „получило по рукам“ от операционной системы за попытку влезть в область памяти, ему не принадлежащей. Когда вы получаете сообщение об этом (рис. 2.28), вам рассказывают, кто (первое число) и куда (второе число) пытался залезть. Первое число предоставляет адрес инструкции, попытавшейся нарушить границы, а второе указывает, куда именно хотела обратиться инструкция-нарушительница.

Вернитесь в среду, выберите команду Search/Find Error, введите адрес ошибки (первое число в сообщении) в поле ввода диалогового окна, щелкните на кнопке ОК и читайте подходящую молитву. Если вам повезет, в окне появится строка, вызвавшая ошибку. Гораздо чаще этого не происходит, так как ошибка оказывается где-то в VCL или библиотеке, скомпилированной без отладочной информации, и вызывает ее передача неверного параметра в функцию, при отработке которого и происходит ошибка доступа.

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

Stack Overflow

Переполнение стека (stack overflow) — ошибка, появляющаяся в 32-битовом приложении гораздо реже, чем в 16 битовом, так как размер стека в этом случае существенно больше. Практически есть только один путь получить эту ошибку в Delphi 4 — попасть в бесконечную рекурсию. Например, приведенная ниже функция неминуемо должна вызвать переполнение стека.

function BlowTheStack(I: Integer); Integer;

var J: Integer;

begin

J:= 2;

Result:= BlowTheStack(I*J);

end;

Каждый раз при рекурсивном вызове в стеке резервируется место для локальной переменной J и адреса возврата. Поскольку условия возврата из рекурсии нет, переполнение стека неминуемо.

Конечно же, бесконечная рекурсия — не единственная причина возникновения ошибки такого рода, но это первое, что стоит отследить при переполнении стека.

External Exceptions

Сообщение об ошибке External exception, показанное на рис. 2.27, может вызываться порожденной приложением исключительной ситуацией, перехваченной He-Delphi-модулем (DLL). Коды ошибок определены в файле WINDOWS.PAS, включенном в поставку Delphi; их символические имена имеют вид STATUSxxxxx. Например, показанная на рис. 2.28, исключительная ситуация C000001D— исключительная ситуация STATUSILLEGALINSTRUCTION. Это, конечно, позволяет судить о том, что произошло, но не дает никакой информации о том, где это произошло, так что единственный способ найти ошибку — разделять и властвовать, т. е. пересмотреть приложение до возникновения исключительной ситуации.

Рис. 2.27. Сообщение о нарушении доступа

Рис. 2.28. Сообщение о внешней исключительной ситуации

Использование отладчика TD32.EXE

Может случиться, что, несмотря на все возможности отладчика Delphi 4, вы столкнетесь с совершенно неотслежимаемой ошибкой. Если это произойдет, примите мои искренние соболезнования. Можете попытаться использовать отдельный отладчик фирмы Borland (TD32.EXE), поставляемый в комплекте Turbo Assembler или Borland C++. В дополнении ко всему, что есть в отладчике Delphi, TD32 имеет и то, чего в Delphi нет. В частности, TD32 позволяет установить аппаратные точки останова, что означает, например, останов при обращении к портам ввода-вывода или к памяти. Это тяжелая работа, и я могу только посочувствовать вам, если вы за нее беретесь. И учтите, что такая работа требует знания как системного программного, так и аппаратного обеспечения.