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

    ГЛАВА  27 

    Стив Шафер 

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

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

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

    Тестирование приложения 

    Создание приложений с использованием возможностей условной компиляции и 
проверок Delphi. 

    Использование интегрированного отладчика Delphi 

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

    Отладка "в лоб" 

    Иногда проще набросать фрагмент и понять, как он работает, чем отладить 
программу целиком. 

    В поисках последней ошибки 

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

638


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

    Лучший путь исключить ошибки в программе — защититься от них еще при 
написании кода. Надежное приложение — приложение, создаваемое с возможностью 
легко и просто отлаживать его. Вот основные советы, которые помогут вам 
уменьшить количество ошибок при разработке программ. 
    •   Ваше приложение должно быть хорошо организовано. Разделите программу 
на модули, каждый из которых выполняет определенные задачи. Например, если 
код, создающий отчет, разнесен по десяти модулям, время отладки такого кода 
увеличится даже более чем в десять раз (хотя бы за счет поиска нужной строки в 
десяти модулях). Конечно же, вы можете вызывать подпрограммы из других 
модулей, но они должны быть созданы для выполнения четко поставленной задачи. 
Глупо размещать одну половину выполняемой операции в процедуре в одном модуле, 
а вторую половину — в другой процедуре (тем более — в другом модуле). Пусть 
это примитивный совет, но он является одним из самых действенных! Порядок — 
прежде всего! Порядок в мыслях и в программе! 
    •   Защититесь от дурака. Если ваша процедура не может переварить 
некорректные данные и вызвать тем самым крах всей системы, проверьте 
целостность входных данных, прежде чем работать с ними. Однако помните: если 
^истемсш^сможет^ врс_пол^зойа1ься-любой дурак, значит, только дурак и будет ею 
пользоваться. Не увлекайтесь чрезмерной защитой, которая неумолимо будет 
отбирать время и ресурсы, необходимые для выполнения более важных задач. 
    •   Используйте отладочный вариант вашей программы. В отладочной версии 
программы содержится дополнительный код, цель которого — отследить выполнение 
программы, убедиться в корректности ее работы и упростить отладку вашего 
приложения. Именно об этом и рассказывается .в следующем подразделе. 

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

    Те, кто участвовали в "полевых испытаниях" (известных как 
бета-тестрирование) коммерческих программ, наверняка обратили внимание, что 
такие версии программ более медлительны, гораздо более "разговорчивы" и 
размером побольше окончательных версий программ. Может быть, разработчик 
спешил и выпустил "сырой" продукт, который будет улучшать перед выпуском 
окончательного варианта? Так тоже бывает, но главная причина в другом: в 
бета-версии содержится тестовый и отладочный коды, используемые разработчиком 
для проверки корректности работы программы. 
    Delphi позволяет очень легко внести тестовый и отладочный коды в 
приложение. Например, вы хотите создать приложение работы с базой данных и 
использовать быстрый, но, возможно, несколько рискованный алгоритм сортировки 
данных. Как же убедиться в корректности его работы? Один из путей — 
использовать в приложении два алгоритма одновременно (быстрый, но рискованный, 
и медленный, но проверенный), затем сравнить результаты работы обоих 
алгоритмов. Конечно же, этот вариант используется только в бета-версии, и 
после всестороннего тестирования, если все работает отлично и без сбоев, в 
конечной версии продукта останется только быстрый (и после такого тестирования 
— уже не рискованный) метод сортировки. 
    Для этого вам вовсе не надо использовать два разных текста программ — 
воспользуйтесь возможностью условного компилирования. Вы можете определить 
символ (я обычно использую Debug, но вы свободны в* вашем выборе) для 
пере-ключения'между коммерческой и отладочной версиями вашего кода с 
использованием директив $IFDEF, $IFNDEF, $ELSE и $ENDIF. Вот пример 
использования "медленного" алгоритма в отладочной версии. 

    DataSet   :=  GetData;   //Получение  данных  для  сортировки. 
    {$ifdef  Debug} 
    TestResultSet   :=  Sort_Tortoise(DataSet);   //Медленно и надежно. 
    {$endif} 
    ResultSet := Sort_Hare(DataSet); //Быстро и рискованно. 
    {$ifdef Debug} 
    if not CompareData(ResultSet, TestResultSet) then 
    //Результаты совпали? 
    Raise Exception.Create('Сортировка в DataSorting некорректна'); ($endif} 
    Если определен символ Debug, код принимает следующий вид. 
    DataSet := GetData; //Получение данных для сортировки. TestResultSet := 
Sort_Tortoise(DataSet); //Медленно и надежно. ResultSet := Sort_Hare(DataSet); 
//Быстро и рискованно. if not CompareData(ResultSet, TestResultSet) then 
    //Результаты совпали? Raise Exception.Create('Сортировка в DataSorting некорректна'); 


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

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

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

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

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

    {$define  Debug} 

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

    {$undef Debug} 

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

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


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


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

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

    640 Часть VI. Сложный материал 

    •   Stack Frames. Если эта установка включена, компилятор всегда включает 
в функцию код для генерации кадра стека, даже если код не использует стек. Как 
и в случае оптимизации, вам вряд ли стоит изменять эту установку. 
Ло-калыТыё'директивы компилятора — $W+ и $W-. 
    •   Range Checking. Проверка диапазона перехватывает ошибки, вызванные 
выходом за пределы массива или строки. Однако дополнительный код сдерживает 
выполнение программы и, по всей видимости, вы отключите эту опцию в 
коммерческой версии. Директивы компилятора для включения и отключения проверки 
— $R+ и $R-. 
    •   Assertions (С). Эта опция более полно описана в следующем разделе. 
Использование данного типа проверок позволяет быстро и просто добавить 
проверки в код. Естественно, в коммерческой версии вы захотите отключить эту 
возможность. Директивы компилятора— $С+ и $С-. 
    •   Overflow checking (Q). Проверка на переполнение позволяет выяснить, не 
является ли результат выполнения целочисленной операции слишком большим для 
размещения его в переменной. Подобно опции Range Checking, данная опция 
полезна только при отладке, и в коммерческой версии, как правило, отключается. 
Директивы компилятора— $Q+ и $Q-. 
    Отладочная версия вашего кода, вероятно, будет больше по размеру и 
медленнее коммерческой версии. Поэтому не передайте случайно конечному 
пользователю отладочную версию! 

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

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

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

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

    procedure  Foo(Count:   Cardinal); begin 
    Assert (Count   <   SizeOf(Word)); 
    end; 

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

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

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

    {$ifdef Debug} {$ASSERTIONS ON} {$else} 
    f$ASSERTIONS OFF} {$endif} 

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

    function CountMe: Integer; 
    const ReferenceCount: Integer = 0; 
    begin 
    Inc(ReferenceCount) ; 


    Глава 27. Тестирование и отладка 
    641 21  Использование Delphi 3. Специальное издание 


    Result := ReferenceCount; end; 

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

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

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

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

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

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

    Давным-давно, когда компьютеры были большими, а именно — лет двадцать 
назад, отладка заключалась в копании в огромных кипах бумаги с исходными 
текстами и распечатках результатов работы и потреблении невероятного 
количества кофеина, а зачастую и анальгина... Современный программист, 
особенно если он программирует на Delphi, больше не чернорабочий от 
программирования с крепкими от переворачивания центнеров бумаги мускулами, а 
"белый воротничок", окруженный разнообразными "электронными мухобойками" 
(вероятно, все знают, что программисты называют ошибк\ bug, т.е. жучок, клоп. 
—Прим. ред.), предоставляемыми Delphi. 
    Интегрированный отладчик Delphi переполнен полезными, не очень полезными и 
совсем бесполезными (но таких мало) возможностями, и кажется, что изучить их 
просто невозможно. Однако использование отладчика Delphi просто, как апельсин: 
ведь когда вы компилируете свою программу и запускаете ее из среды Delphi, вы 
уже пользуетесь встроенным отладчиком, хотя, вероятно, и не подозреваете об 
этом. Как всегда, 90% проблем решается 10% возможностей программного 
обеспечения, а потому все разнообразие работы с отладчиком вы можете изучить 
позднее, при решении оставшихся 10% ваших проблем. 

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

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

    642	Часть VI. Сложный материал 

    Ниже перечислены опции вкладки Preferences и их функции. 
    •   Integrated Debugging. Позволяет включать и отключать встроенный 
отладчик, Если вы отключите отладчик, отладочные команды в меню Run станут недоступными. 
    Если у вас возникли проблемы при запуске приложения из интегрированной 
среды, возможно, это вызвано конфликтом встроенного отладчика с другими 
приложениями, запущенными в системе. Попробуйте отключить встроенный отладчик 
^J^fj^J^0 пом_°_:>кет' ^SHEJlKESffi1 — проблема кроется в одном из уже 
запущенных в системе приложений. ЛучшймГрешением будет перезагрузка и 
последующий запуск только абсолютно необходимых приложений. 
    I Совет 1 

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

    Step Program Block. Эта опция определяет, должен ли отладчик 
останавливаться перед началом выполнения основного блока begin. . . end при 
трассировке программы. Обычно данная опция отключена, и включать ее имеет 
смысл при добавлении кода в основной блок программы либо при отладке 
консольного приложения. 
    Hide Designers on Run. Когда эта опция включена, окно Object Inspector и 
формы, использующиеся при разработке приложения, перед запуском программы на 
выполнение закрываются. Отключение опции позволяет запускать программу 
быстрее, но эффект перекрывается используемыми незакрытыми ресурсами 
приложения. Впрочем, будет ли выбрана эта опция, зависит от пользователя. 
    Break on Exception. При включенной опции IDE всегда перехватывает 
исключительные ситуации и выводит окно сообщения, даже если в программе 
исключительная ситуация обрабатывается блоком try,._._.except. Включение этой 
опций упростит отладку, так как вь!1врдйМЫе"сбоб"щёнияi при~зтом" СЗуЯ^^более 
информативными, чем сообщения обработчика, установленные по умолчанию 
"(сравните рис. 27.5" и27.7). Помимо этого^ 1Т)Е размещает окно редактора 
поверх остальных и выделяет строку, вызвавшую исключительную ситуацию.	- ~
    3 

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

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

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

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

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

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

    Перед началом отладки следует убедиться, что в приложение включена 
отладочная информация. Delphi позволяет включать отладочную информацию 
помодульно, т.е. вы можете исключить ее из надежных и тщательно проверенных модулей. 
    Для компиляции проекта с отладочной информацией следует выполнить команду 
Project^Options и в диалоговом окне Project Options выбрать вкладку Compiler 
(рис. 27.9). 

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

    Рис. 27.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, так что вам не надо отключать их отдельно. 

    644 Часть VI. Сложный материал 

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

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

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

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

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

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

    I Совет I 
    Trace Into. В отличие от предыдущей команды, эта опция отработает 
пошаговую отладку вызываемых процедур и функций. Другими словами, если, 
например, в строке вызывается некая процедура, то при выполнении этой команды 
точка выполнения перейдет на первую строку процедуры. Однако если в строке нет 
таких вызовов, значит, последние две команды идентичны 
    ?удьте.-остоложнь1 пр,и пошаговрй„трдс.?Ироще.рбра_ботчика события 
OnPaint. Поскольку при пошаговой   | отладке окно редактора размещается поверх 
других окон, требуется перерисовка окна приложения, для чего вызывается 
обработчик события OnPaint... Вы попадаете в замкнутый круг, точнее— в 
бесконечный цикл вызовов одного и того же обработчика. Тем не менее стоит лишь 
проследить, чтобы, окна приложения jj^ редактора не перекрывались,~илроблема 
"разреПгитсгСсамахюбЬй.	, < .. •    .   .<    . .--..• 
    Trace to Next Source Line. Иногда ваш код вызывает другой код косвенно, 
например при вызове функции, которая   * запускает обработчик события, или при 
вызове функции Windows API, которая, в свою очередь, запускает функцию 
косвенного вызова. Поскольку такие вызовы косвенные, отладчик не видит 
вызова^щ?отслеж11вает пошаго-    , вого выполнения таких вшововГОднако 
использование описываемой команды приводит к отслеживанию таких   | вызовов и 
останову отладчика на первой строке вызываемой таким образом функции или процедуры. 
    Run to Cursor. Зачастую вам вовсе не хочется в поисках ошибки, 
местоположение которой с какой-то точностью вам известно, пошагово добираться 
до нужного места через сотни, а то и тысячи строк кода. В таком случае просто 
поместите ку?сор_на нужную вам строку П?огр_аммуь1вi ^н?^еддктщэавадщли 
используйте команду Run to СоТЗ'бТГЭтй действия^^щй^ёнтньТ временному 
помещению точки останова в необходимую вам строку программы, и после 
выполнения предшествующего строке кода работа программы приостанавливается. 
Если вы пытаетесь выполнить программу ^цо позиции курсора, который находится в 
строке, не содержащей отдадочЛРЙ~"инФормации' вы получите, сообщение об 
ошибке, показанное на рис. 27.11. 

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

    No code was generaled (от !he cuiertt бпе. 

    Ш   Show Execution Point. Эта команда заставляет среду разработки открыть 
окно редактора и показать выполняемую в '	настоящее время строку программы. 
Она полезна в случаях, когда вы, например, закрыли или свернули окно редактора 
во время отладки (обычно при нормальном состоянии окна отладчик делает это автоматически). 
    •   Program Pause. Выбор этой команды немедленно останавливает выполнение 
программы. Она особенно полезна при зацикливании программы. 
    •   Program Reset. Если вы достаточно "наотлаживались" и хотите завершить 
работу своей программы или запустить ее заново, используйте эту команду. Она 
немедленно прекратит выполнение программы и вернет вас в среду разработчика 
    Многие команды имеют связанные с ними комбинации клавиш, например <F9> для 
Run. Однако назначения клавиш могут быть изменены во вкладке Editor 
диалогового окна Options. Например, команде Step Over назначена клавиша <F8>, 
но при выборе назначений клавиш в стиле редактора BRIEF назначенной 
комбинацией клавиш становится <Ctrl+Fl 1>. Вы можете увидеть назначенные 
клавиши непосредственно в меню Run, как показано на рис. 27.10. Помимо этого, 
на панели инструментов есть кнопки, вызывающие некоторые из этих команд (вы 
имеете возможность также удалить некоторые из них или добавить новые). 

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

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

    646	Часть VI. Сложный материал 

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

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

    Вы не можете просмотреть массив BigArray, так как 1 000 элементов просто 
не поместятся в окне (да и пролистать ненужные вам 825 элементов — работенка 
немалая!). Вместо этого вы просите показать вам значение BigArray [826] и 
устанавливаете параметр Repeat Count равным 8. При этом вам будут показаны 
значения восьми элементов массива — OT8_26jrioJ!33 
    Использование поля Digits позволяет определить количество значащих цифр 
при выводе числа с плавающей точкой. 
    Отключенная опция Enabled предотвращает вывод значения переменной, однако 
среда будет хранить все параметры, заданные для ее просмотра. Так, вы можете 
в^е^енно^брать с_экр_ана информацию о переменной, которая в настоящий момент 
вам не нужна, и быстро восстановить ее на экране при необходимости без 
повторного ввода характеристик просмотра. 
    Б диалоговом окне имеется также набор переключателей для выбора способа 
представления переменной. Значение Default позволяет среде разобраться с типом 
переменной самостоятельно. Как правило, такое решение оптимально, однако в 
любой момент вы можете представить переменную как переменную того типа, 
который вас интересует. Опция Метоп/ dump представляет информацию как набор 
байтов, что бывает полезно, когда необходимо увидеть внутреннее представление 
информации в переменной. 

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

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

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

    Смысл приведенных сообщений поясняется ниже. 
    •   Variable 'X' inaccessible here due to optimization (Переменная 'X' 
недоступна из-за оптимизации). В этой точке значение переменной просмотреть 
невозможно (иногда это можно сделать в другом месте^тгюграммы), так как для 
нее не выделена память из-за оптимизации программы компилятором. 
    •   Symbol was eliminated by linker (Переменная удалена компоновщиком). 
Переменная удалена из кода программы компоновщиком, так как на нее нет ни 
одной ссылки в тексте программы. 
    Отладчик также выводит сообщение об ошибке, если имя переменной написано 
неправильно, например вы запросили значение Foo [ 5 ], в то время как 
переменная Foo массивом не является. 

    заметку 

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

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


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

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

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

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

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

    Значение переменной — на блюдечке с голубой каемочкой 

    В Delphi 3 есть одна удобная возможность, которая в дальнейшем может 
вытеснить окна просмотра: во время паузы в работе программы при отладке 
установите курсор на интересующую вас переменную, и тут же всплывет маленькое 
окошко с ее значением. Что может быть проще? Конечно, если вы хотите наблюдать 
за множеством переменных одновременно, окно Watch List к вашим услугам! 

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

    Точка останова (breakpoint) — своеобразный знак STOP для отладчика (на 
полосе слева в окне редактора она и выглядит похоже — как маленький красный 
значок). Когда ваше приложение запущено под отладчиком и доходит до строки, в 
которой находится точка останова, оно прекращает работу и ждет ваших 
дальнейших распоряжений. Такие точки могут быть условными и безусловными. 
Отладчик всегда останавливается на точке безусловного „останова и может 
останавливаться в точке условного останова, когда выполнено условие. 
Интегрированный отладчик Delphi поддерживает два типа условий — логическое и 
по количеству проходов. Ниже рассмотрены оба типа. 
    Установить точки останова можно следующими способами. 
    1.   Поместите курсор редактирования на выбранную строку программы и 
нажмите клавишу команды Toggle Breakpoint (по умолчанию это клавиша <F5>) для 
установки или удаления точки останова в этой строке. То же самое можно 
выполнить и с помощью контекстного меню. 
    2.   Выберите команду Run^Add Breakpoint, и откроется диалоговое окно Edit 
breakpoint (рис. 27.15). Для установки простейшей точки останова просто 
щелкните на кнопке New. Вы также можете использовать поля Filename и Line 
Number для установки точек останова в другом файле или строке за пределами 
текущей позиции курсора. Поля Condition и Pass count используются для 
установки точки условного останова. 

    648 Часть VI. Сложный материал 

    После установки одной или нескольких точек останова можете использовать 
окно Breakpoint List для управления ими. 
    Для вызова окна Breakpoint List выберите команду View=>Breakpoints (рис. 
27.16). В этом окне можете щелкнуть на строке конкретной точки правой кнопкой 
мыши и в контекстном меню отключить точку останова с помощью команды Disable 
(вновь включить точку останова можно с помощью команды Enable) или удалить ее 
с помощью команды Delete. Команды View Source и Edit Source активизируют окно 
с текущим файлом исходного текста, при этом команда Edit Source устанавливает 
курсор в строку с точкой останова. Команда Properties выводит диалоговое окно 
Edit breakpoint, показанное на рис. 27.15, позволяя тем самым изменять 
параметры точки останова. 

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

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

    После щелчка правой кнопкой мыши в окне при невыбранной точке останова 
выводится контекстное меню, в котором команда Add служит для добавления новой 
точки, Delete All удаляет все точки останова, а команды Disable All и Enable 
АН отключают или включают все точки останова в списке. 
    Для превращения безусловной точки останова в условную необходимо вызвать 
диалоговое окно Edit breakpoint (см. рис. 27.15) и ввести условное выражение 
или количество проходов в соответствующие поля. 
    Условное выражение, введенное в поле Condition, может быть любым 
логическим выражением. По достижении точки останова отладчик вычисляет 
значение выражения и, если результат ложен, продолжает выполнение программы. 
Если выражение истинно, выполнение программы приостанавливается. Как обычно, 
выражение не должно использовать вызов функции. Такие точки останова полезны, 
если вы можете попасть в интересующий вас фрагмент кода различными путями, но 
вы хотите остановить выполнение программы только при достижении каких-либо 
конкретных условий. 
    Ненулевое значение, введенное в поле Pass count, дает отладчику задание 
продолжать выполнение программы при прохождении через точку останова, пока 
через нее не будет выполнено соответствующее количество проходов. При каждом 
проходе через точку останова отладчик уменьшает значение счетчика на единицу и 
по достижении нулевого значения программа приостанавливается. Такой метод 
полезен при работе с циклами, особенно если вы знаете, что ошибка происходит 
после определенного количества циклов. 

    На заметку 

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

    Отладка DLL 

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

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


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

    Окно CPU предоставляет возможность увидеть работу приложения на уровне 
языка ассемблера. Те, кто использовали отладчик Turbo Debugger for Windows, 
должны знать возможности, предоставляемые таким окном. Эффективное его 
использование предполагает знание ассемблера Intel x86 и архитектуры 
процессора, так что, если вы не вполне уверенно себя чувствуете, можете 
пропустить этот раздел. 
    Окно CPU требуется крайне редко, но если в нем возникла необходимость, 
значит, это действительно необходимость. Это — ultima ratio, последний довод, 
и используется он в безвыходных положениях, когда обычная трассировка кода не 
позволяет найти, понять и исправить 5дЖкй. Только в таких случаях окно CPU и 
проход по ассемблерным инструкциям может приподнять завесу над причиной 
возникновения ошибок... 
    Чтобы использовать окно CPU, необходимо его включить (по умолчанию оно 
отключено). Для этого придется использовать программу RegEdit из поставки 
Windows 95 (Windows NT). Запустите RegEdit и пробирайтесь по иерархическому 
дереву папок. Сперва откройте папку HKEY_CURRENT_USER. Во вложенной ветви 
зайдите в папку Software, затем — в Borland, Delphi и, наконец, в 3. Все! 
    Одна из папок называется Debugging. Щелкните на ней и, когда она 
откроется, появится список пар Имя/Данные в окошке справа. Добавьте новое 
значение с именем EnableCPU и значением 1. 
    При следующем запуске Delphi вы увидите новый подпункт меню View^CPU Window. 
    Для вывода окна выберите View=>CPU Window. Как показано на рис. 27.17, 
окно разделено на четыре панели. Вы можете изменять их размеры, перетаскивая границы. 

    Рис. 27.17. Окно CPU Перечислим панели. 

    Я   Code pane. Панель кода представляет дизассемблированный код в 
окрестности текущей точки выполнения (если вы не отлаживаете приложение, окно 
будет полупустым). Кроме того, панель показывает исходный текст строк, 
соответствующих выполняемому коду. В окне редактирования точка выполнения 
индицируется маленьким зеленым значком. При пошаговом проходе значок точки 
выполнения синхронно перемещается по окну CPU и окну редактирования. 
    Я   Register pane. В панели регистров отображается содержимое 16 регистров 
процессора. Значения регистров, изменившиеся в результате выполнения последней 
операции, выделены красным цветом. 
    •   Flags pane. Панель флагов показывает состояние 14 флагов процессора. 
Установленный флаг представляется значением 1, сброшенный флаг — значением 0. 
В зависимости от процессора некоторые флаги могут быть недоступными. 
    в   Stack pane. Панель стека показывает содержимое стека приложения. Вы 
можете изменять представление содержимого стека с помощью контекстного меню. 
    •   Data pane. По умолчанию в панели данных выводится содержимое 
глобального сегмента данных приложения. Ее вид можно изменить так же, как и 
вид панели стека. 
    .Каждая из панелей в окне CPU имеет собственное контекстное меню. Вы 
можете поэкспериментировать с пунктами меню и получить более полное 
представление о возможностях манипулирования панелями. 

    650 Часть VI. Сложный материал 


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

    Окна Thread Status, Modules и Call Stack предоставляют дополнительную 
информацию, которая может быть полезна при отладке приложения. 
    В окне Thread Status перечислены все активные подзадачи текущего процесса. 
Для просмотра состояний подзадач выберите команду View=>Threads, и на экране 
появится окно Thread Status (рис. 27.18). 

    IU- Thiead Status 


    Рис. 27.18. Использование окна 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 и 
библиотеки, подключенные через другие библиотеки,^гакЖ.§Н.§Л.иотеки, 
загруженные операционной системой. Чтобы увидеть это окно, из ображенное на 
рис. 27.19, выберите команду View^Modules. В окне информация выводится в трех 
столбцах: Name (имя модуля), Address (адрес начала кода модуля) и Path (полный 
путь каталога, из которого был загружен модуль). Информация о каталоге может 
быть важна, если^возможна загр^з_ка мо^ум Heji3,,ipro каталога, из которого 
ожидалась, например в бол ее старой вёрсийГ"Ин"формация об адресе обычно 
используется при отладке в окне CPU. 

    Окно Call Stack 

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

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

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


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

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

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

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

    I Совет 1 

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

    Под силовой отладкой (brute-force debugging), отладкой "в лоб", понимаются 
методы отладки, основанные не на возможностях отладчиков, а на трюках, 
родословная которых, пожалуй, восходит к временам Атанасова и Лебедева, 
создававших первые ЭВМ по обе стороны океана. 
    При разработке программ часто нет необходимости в полной отладке; просто 
хочется з'бедиться^в том, что какая-либо функция работает так, а не иначе (я 
весьма часто попадаю в подобные ситуации, когда использую малознакомые функции 
API или плохо или вовсе недокументированные методы объектов, и мне надо 
провести эксперимент, чтобы выяснить, так ли я п редставляю работу функции). 
    В этих случаях проще забыть об отладчике и просто добавить пару строк кода 
для вывода информации. Для этого есть много путей, и о некоторых из них будет 
рассказано ниже. 

    ПРЕДОСТЕРЕЖЕНИЕ 

    Большинство таких методов — из серии "быстро и грязно", и я бы не 
рекомендовал заменять ими описанные ранее методы тестирования и отладки. 

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

    Один из способов вывода такой информации — ее вывод непосредственно в 
форме. Обычно проще всего создать компонент TLabel или подобный ему для 
непосредственного вывода информации. В таком случае выведенная информация не 
потеряется даже при перерисовке формы. 
    Посмотрите на описания функций ExtractFileDir и ExtractFilePath в 
справочной системе Delphi 3. Я не берусь точно судить по документации о 
различии между этими функциями, но я знаю, что мне делать. Я создаю новое 
приложение (выбрав пункт меню File1* New Application) и помещаю в главную 
форму элемент TButton и два элемента TLabel (форма будет выглядеть так, как на 
рис. 27.21). 
    Дважды щелкните на кнопке TButton и добавьте код к обработчику события OnClick. 

    procedure TForml.ButtonlClick(Sender:   TObject); begin 
    Labell.Caption   := ExtractFileDir(Application.ExeName); 
    Label2.Caption   := ExtractFilePath(Application.ExeName); end;	'—————--    

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

    652	Часть VI. Сложный материал 

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

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

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

    procedure  TForml.ButtonlClick(Sender:   TObject); 
    var MemStat:   TMemoryStatus; 
    begin 
    VirtualAlloc(nil, 1000000, MEM_RESERVE, PAGE_READWRITE); // 1 
MemStat.dwLength := SizeOf(TMemoryStatus);	// 2 GlobalMemoryStatus(MemStat); 
// 3 Memol.Lines.Add(IntToStr(MemStat.dwAvailVirtual)); // 4 
    end; 


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

    Не беспокойтесь о деталях вызова API-функции VirtualAlloc в строке 1. 
Здесь ее вызов требует от операционной системы зарезервировать миллион байтов 
памяти для дальнейшего использования. API-функция GlobalMemoryStatus 
возвращает информацию об использовании памяти приложением и системой в целом. 
Информация возвращается в переменной MemStat, представляющей собой запись типа 
TMemoryStatus. Перед вызовом GlobalMemoryStatus вы передаете системе 
информацию о размере структуры, как в строке 2, а затем вызываете функцию 
(строка 3) и выводите информацию в ТМето в строке 4. 
    Скомпилируйте и запустите программу, щелкните несколько раз на кнопке— и 
увидите, что виртуальная память уменьшается примерно на один мегабайт при 
каждом щелчке, как и ожидалось. На рис. 27.23 показана форма после нескольких 
щелчков на кнопке. 
    ИсполихаЛЕЯТ мет°Д (без вызова VirtualAlloc), я выяснил, что на самом деле 
DLL затребовала около 60 Мбайт (!) виртуальной памяти при загрузке и не 
освободила ее. Даже при том что Windows 95 предоставляет каждому приложению 
двухгигабайтовое адресное пространство, потерю 60 Мбайт сложно проигнорировать... 

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


    ShowMessage 

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

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

    Процедура ShowMessage (из модуля.Dialags) идеально подходит для этой цели. 
Она позволяет вывести строку любой длины в простом модальном диалоговом окне. 
Вам только следует создать строку для вывода и передать ее процедуре (можно 
также использовать MessageDlg, но в нем слишком много шашечек и бантиков, 
которые требуют немалых усилий для достижениятого же эффекта). 
    ShowMessage получает в качестве параметра одну строку, для создания 
которой я предпочитаю использовать функцию Format; она идеально подходит для 
этого, будучи одновременно простым и мощным инструментом в умелых руках. 
    Рассмотрим простой пример. Используем этот метод для вывода информации, 
получаемой от уже использовавшейся функции GlobalMemoryStatus. 
    Создадим новое приложение и поместим TButton в основную форму. Обработчик 
события OnClick будет выглядеть следующим образом. 

    procedure  TForml.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'#13  + 'Total  virtual:   %d'#13   +   
'Available  virtual:   %d', [dwMemoryLoad,   dwTotalPhys,   dwAvailPhys,   
dwTotalPageFile, dwAvailPageFile,   dwTotalVirtual, dwAvailVirtual])); end; 

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

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


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

    Еще один способ вывода отладочной информации— вывод на консоль с 
использованием процедур Write и WriteLn. Вы можете конвертировать проект в 
консольное приложение, например выбрав соответствующую опцию (команду 
Projecf=>'0pt!orfs, вкладку Linker и опцию Generate Console Application) или 
поместив директиву $APPTYPE CONSOLE в главный DPR-файл. Учитывая, что ваше 
приложение— не консольное, "воспользуйтесь возможностями условной компиляции и 
используйте директиву $APPTYPE как показано ниже. 

    {$ifdef Debug} {$APPTYPE CONSOLE} {$endif} 

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

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

    procedure TForml.ButtonlClick(Sender: TObject); 
    var MemStat: TMemoryStatus; 
    begin 
    MemStat.dwLength := SizeOf(TMemoryStatus); 
    GlobalMemoryStatus(MemStat); 
    with MemStat do 


    654 Часть VI. Сложный материал 


    begin 
    WriteLnJ^qrrnatt'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; 

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

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

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

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

    Запись отладочной информации в файл протокола (Log-файл) существенно 
отличается от предыдущих приемов записи, так как это уже нельзя назвать 
"быстро и грязно". Это отличная технология, которую можно использовать в любом 
приложении. ~ ~~	~ ——— 
    Запись в файл протокола выполняется так_же_,_как _и_в_ьпюд^й^аш?0ль, но 
вместо WriteLn (...) используется WriteLn (LogFile, . . . ), где LogFile — имя 
файловой переменной типа TextFile. Надо также не забывать открывать этот файл 
в начале работы приложения и закрывать — в конце. Проще всего этого добиться, 
поместив соответствующий код в свой модуль , который благодаря возможности 
условной компиляции подключается только в отладочной версии вашей программы. 
Вы можете найти такой модуль на дополнительно распространяемой дискете и 
использовать его в своих приложениях. 

    Листинг 27.1. \UDELPHi3\Chap27\ULOG. PAS. Модуль протоколирования 
отладочной информации 

    unit uLog; 
    interface 
    procedure Log(S: String); 
    implementation 
    uses 
    Windows, SysUtils; var 
    LogFile: TextFile; 
    LogCriticalSection: TRtlCriticalSection; 


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


    pj:9cedu.r,e_,Lqg (3: string) ; var 
    SystemTime: TSystemTime; 
    FileTime: TFileTime; begin 
    GetSystemTime(SystemTime); 
    SystemTimeToFileTime(SystemTime, FileTime); 
EnterCriticalSection(LogCriticalSection); WriteLn(LogFile, Format('%s %.8x%.8x %s', 
    [FormatDateTime('yy.mm.dd hh.mm.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.txt', 
    [ParamStr(0), 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 ('V, 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), что обеспечивает доступ к файлу только 
одной подзадачи в один момент времени. Подробнее об этом вы можете прочесть в 
главе 24, "Работа с подзадачами". 

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


    656	Часть VI. Сложный материал 


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

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

    В погоне за дикими жуками 

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

    Error Setting Debug Exception Hook 

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

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


    Access Violation 

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

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

    щение об этом (рис. 27.28), вам рассказывают, кто (первое число) и куда 
(второе число) пытался залезть. Пещюе^исло предоставляет адрес инструкции, 
попытавшейся нарушить границы, а второе указывает, куда именно хотела 
обратиться инструкция-нарушительница.	"" ••-—--._„ 
    Вернитесь в среду. щбег^ите_крманду Search^pincl Error,.введите адрес 
ошибки (первое число в сообщении) в поле ввода диалогового окна, щелкните на 
кнопке ОК и читайте подходящую молитву. Если вам повезет, в окне редактора 
появится строка, вызвавшая ошибку. Гораздо чаще этого не происходит, так как 
ошибка оказывается где-то в VCL или библиотеке, скомпилированной без 
отладочной информации, и вызывает ее передача неверного параметра в функцию, 
при отработке которого и происходит ошибка доступа. 
    Даже скомпилировав VCL с отладочной информацией, вы, скорее всего, сможете 
получить только имя функции, вызвавшей ошибку, и вам все равно придется 
потратить немало времени на поиски ошибки где-то совсем в другом месте. 

    Stack Overflow 

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

    function BlowTheStack (I:   Integer):   Integer; var  J:   Integer; begin 
J   :=  2; 
    Result   :=  BlowTheStack(I*J); end; 

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

    External Exceptions 

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

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

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


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

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

    658 Часть VI. Сложный материал 


    Резюме 

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

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