![](43049_html_m4b2248c4.png)
Модульность — это свойство программы, связанное с декомпозицией ее на ряд отдельных фрагментов, которые компилируются по отдельности, но могут устанавливать связи между собой. Связи между модулями — это их представление друг о друге.
Доступ к данным объекта не через его методы запрещен! Кроме того, объекты должны ограничивать свои операции только их собственными данными и не должны быть связанными ни с какими глобальными переменными, а также не должны изменять их.
Правильное разделение программы на модули является почти такой же сложной задачей, как выбор правильного набора абстракций. Модули исполняют роль физических контейнеров, в которые помещаются определения классов и объектов при логическом проектировании системы. Для описания небольших задач допустимо описание всех классов и объектов в одном модуле. Однако для большинства программ лучшим решением будет сгруппировать в отдельный модуль логически связанные классы и объекты, оставив открытыми те элементы, которые совершенно необходимо видеть другим модулям.
В традиционном структурном программировании модульность — это искусство раскладывать программы на части так, чтобы в один контейнер попадали подпрограммы, использующие друг друга или изменяемые вместе. В ООП ситуация несколько иная: необходимо физически разделить классы и объекты, составляющие логическую структуру проекта.
Особенности системы, подверженные изменениям, следует скрывать в отдельном модуле. В качестве межмодульных можно использовать только те элементы, вероятность изменения которых мала. Все структуры данных должны быть обособлены в модуле; доступ к данным из модуля должен осуществляться только через процедуры данного модуля. Другими словами, следует стремиться построить модули так, чтобы объединить логически связанные абстракции и минимизировать взаимные связи между модулями.
Модульность — это свойство системы, которая была разложена на внутренне связные, но слабо связанные между собой модули.
Правила разделения системы на модули.
Распределение классов и объектов по модулям должно учитывать то, что модули служат в качестве элементарных и неделимых блоков программы.
Многие компиляторы создают отдельный сегмент кода для каждого модуля, поэтому могут появиться ограничения на размер модуля. Динамика вызовов подпрограмм и расположение описаний внутри модулей может сильно повлиять на локальность ссылок и управление страницами виртуальной памяти.
Иерархия. Значительное упрощение в понимании сложных задач достигается за счет образования из абстракций иерархической структуры.
Иерархия — расположение частей или элементов целого от высшего к низшему, это упорядочение абстракций, расположение их по уровням. Одним из важных видов иерархии является наследование.
Программист для решения определенного класса задач может строить иерархию классов, в которой, и это самое главное, каждый следующий производный класс имеет доступ (наследует) к данным и действиям всех своих предшественников (прародителей). Потомок получает в свое распоряжение все, что принадлежало его предку. Потомок может добавить новые методы, свойства или поля и изменить реализацию любого метода, но не может их уничтожить. Согласно определению наследования, поля и методы предка доступны его потомку. Если в потомке создается одноименное поле или метод, можно говорить о перекрытии полей и методов.
В ООП используют два вида иерархии.
Иерархия «целое-часть» показывает, что некоторые абстракции включены в некоторую абстракцию как ее части, например, строение цветка описывается следующими частями: цветоложе, пестик, тычинки, цветоножка, завязь, лепестки. Этот вариант иерархии используется в процессе разбиения системы на разных этапах проектирования (на логическом уровне — при декомпозиции предметной области на объекты, на физическом уровне — при декомпозиции системы на модули и при выделении отдельных процессов в мультипроцессорной системе).
Иерархия «общее—частное» — показывает, что некоторая абстракция является частным случаем другой абстракции, например, ель — это разновидность хвойных деревьев, а деревья — это часть растительного мира планеты. Используется при разработке структуры классов, когда сложные классы строятся на базе более простых путем добавления к ним новых характеристик и, возможно, уточнения имеющихся.
Разработайте класс Точка.Решение
Определим новый класс TPoint (точка). Точка определяется координатами х и у. Объекты типа TPoint можно сделать видимыми или невидимыми, задать цвет изображения, переместить и т. д.
Полная структура класса TPoint имеет вид:
![](43049_html_b18878f.png)
TPosition, указанный в скобках после зарезервированного слова class, сообщает компилятору, что TPoint является «потомком» класса TPosition и соответственно наследует все поля и методы этого класса (в частности, поля Fx и Fy, методы GetX и GetY).
Класс TPoint описывает новые поля, определяющие видимость (FVisible) и цвет (FColor), и методы определить видимость (IsVisible), отобразить (Show), спрятать (Hide), переместить (Move).
Конструктор Create и деструктор Destroy переопределяются.
Отметим, что новый класс автоматически получает все данные и методы своих предков, то есть экземпляр класса TPoint содержит все данные (поля) и методы типа TPosition. Таким образом, в иерархичном дереве классов по мере удаления от корня будут встречаться все более сложные классы, экземплярами которых будут объекты с более сложной структурой и поведением.
Доступ к полям и методам, описанным в классе-родителе, осуществляется так же, как к собственным.
Типизация. Напомним, тип — это точная характеристика свойств, включая структуру и поведение, относящуюся к некоторой совокупности объектов. Типизация — это ограничение, которое накладывается на класс объектов и препятствует взаимозаменяемости различных классов или сильно сужает возможность такой замены.
Использование принципа типизации обеспечивает:
раннее обнаружение ошибок, связанных с недопустимыми операциями над программными объектами (ошибки обнаруживаются на этапе компиляции программы при проверке допустимости выполнения данной операции над программным объектом);
упрощение документирования;
возможность генерации более эффективного кода.
ООП возможна статическая и динамическая связь имени объекта и его типа. В первом случае это означает определение типов переменных во время компиляции. Во втором — тип выражения определяется во время исполнения приложения. Из принципов динамической связи и наследования вытекает очень важное свойство, присущее объектам — полиморфизм. Полиморфизм — это выделение некоторого действия, т. е. действие должно иметь имя, и создание средств использования действия объектами иерархии, причем каждый класс реализует это действие так, как оно для него подходит.
Итак, при создании иерархии классов может обнаружиться, что некоторые свойства объектов, сохраняя название, изменяются по сути.
Для реализации таких иерархий должен быть предусмотрен полиморфизм, обеспечивающий возможность задания различных реализаций некоторого единого по названию метода для классов различных уровней иерархии. В ООП такой полиморфизм называется простым, а методы, имеющие одинаковое название, — статическими полиморфными. В ранее рассмотренных упражнениях статическим полиморфным методом является, например, конструктор Create.
Совокупность полиморфных методов с одним именем для иерархии классов образует единый полиморфный метод иерархии, в котором реализация полиморфного метода для конкретного класса представляет отдельный аспект.
Сложный полиморфизм. Полиморфными объектами, или полиморфными переменными, называются переменные, которым в процессе выполнения программы может быть присвоено значение, тип которого отличается от типа переменной.
В языках со строгой типизацией такая ситуация может возникнуть
при передаче объекта типа класса-потомка в качестве фактического параметра подпрограмме, в которой этот параметр описан как параметр типа класса-родителя (явно — в списке параметров или неявно — в качестве внутреннего параметра, используемого при вызове методов — Self);
при работе с указателями, когда на объект класса-родителя присваивается адрес объекта класса-потомка.
Тип полиморфного объекта становится известным только на этапе выполнения программы, соответственно, при вызове полиморфного метода для такого объекта нужный аспект также должен выполняться на этапе выполнения. Для этого в языке должен быть реализован механизм позднего связывания, позволяющий определять тип объекта и аспект полиморфного метода, к которому идет обращение в программе, на этапе ее выполнения.
С помощью механизма позднего связывания реализуется оперативная перестройка программы в соответствии с типами используемых объектов.
Рассмотрим это свойство на практике.
Упражнение. Разработайте класс Окружность. Решение
Определим новый класс TCircle (окружность). Окружность определяется центром с координатами х, у и радиусом г. Объекты этого типа можно сделать видимыми или невидимыми, задать цвет изображения, переместить и т. д. В связи с этим можно определить новый класс как потомок класса TPoint:
TCircle = class (TPoint)
Private
Fr: Integer; {радиус}
Public
Constructor Create(InitX, InitY, InitR: Integer); Destructor Destroy;
Function GetR: Integer; {возвращает значение радиуса}
Procedure Show; {спрятать}
Procedure Hide; {отобразить}
Procedure Move (NewX, NewY: Integer); {переместить}
End;
Классы TPoint и TCircle связаны отношением наследования и содержат методы Hide (спрятать), Show (отобразить) и Move (переместить). Очевидно, что методы Show и Hide для каждого класса свои, но логика метода Move совпадает:
Переместить:
Спрятать объект; {вызов метода Hide}
Изменить координаты объекта; {x:=NewX; y:=NewY}
Отобразить объект; {вызов метода Show}
Поэтому естественным было бы желание определить метод Move только в TPoint так, чтобы класс-потомок TCircle унаследовал его без определения. Но в методе Move ссылки на методы Hide и Show формируются на стадии компиляции. Это жесткая, статическая связь, и без ее «разрыва», т. е. реализации механизма более позднего формирования ссылок на методы не на стадии компиляции, а на стадии выполнения (динамическое связывание), реализовать это невозможно. Добавление к заголовку метода зарезервированного слова virtual объявляет его виртуальным, т. е. связь с этим методом устанавливает на стадии выполнения программы. Перепишем описание классов следующим образом:
TPoint = class (TPosition)
Procedure Show; virtual; Procedure Hide; virtual;
end;
TCircle = class (TPoint)
Procedure Show; override; Procedure Hide; override;
end;
Директива override используется для переопределения функциональности метода-предка, она необходима для поддержки полиморфной иерархии.
Реализация динамической связи для объектов, имеющих хотя бы один виртуальный метод, осуществляется с помощью таблицы виртуальных методов (ТВМ). Она содержит адреса виртуальных методов. Для каждого класса во время компиляции программы строится одна ТВМ (рис. 1.3.2).
Формирование связи между экземпляром класса (объектом) и ТВМ осуществляет конструктор.
Отметим, что методы, работающие с полиморфными объектами — это всегда методы классов-предков, описывающие общие моменты поведения объектов. В сложной иерархии, таким образом, можно выделить семейство классов со схожим поведением объектов. Они образуют поддеревья, в корне которых находится класс, определяющий общие моменты поведения.
Итак, мы смогли исключить метод Move из описания класса TCircle, сделав его полиморфным. Все объекты классов TPoint и TCircle будут использовать его, причем так, как им это необходимо.
Сформулируем правила, которые важно выполнять при работе с виртуальными методами:
если в некотором классе метод описан как виртуальный, то все производные классы, включающие метод с тем же именем, должны описать этот метод как полиморфный (override). Нельзя заменить виртуальный метод статическим;
порядок расположения, количество и типы формальных параметров в одноименных виртуальных методах должны оставаться неизменными.
В дополнении к виртуальным методам, для реализации полиморфизма в Object Pascal используются динамические методы. По возможностям наследования и перекрытия они аналогичны виртуальным методам, но доступ к ним выполняется через таблицу динамических методов (ТДМ). ТДМ хранит адреса только тех динамических методов, которые определены в данном классе. Такой подход позволяет снизить расход памяти при большом количестве этих методов и самих классов.
На каждый динамический метод приходится только одна ссылка, представленная индексом, по которому и происходит поиск метода для вызова.
Для объявления метода динамическим используется директива dynamic. Перекрытие динамических методов производится так же, как и виртуальных — с использованием ключевого слова override.
Абстрактные методы
Абстрактные методы используются при объявлении методов, реализация которых откладывается. Такие методы в классе описываются служебным словом abstract и обязательно переопределяются в потомках класса.
Класс, в состав которого входят методы с отложенной реализацией, называется абстрактным. Создавать объекты абстрактных классов запрещается.
Упражнение 1.3.4. Разработайте родительский класс для рисования геометрических фигур.
Решение
Выделим минимальный объем свойств и методов, которые определяют все геометрические фигуры. Во-первых, это точка, относительно которой будет определяться положение фигуры на экране. Во-вторых, это цвет отображаемой геометрической фигуры. Кроме того, определим методы: скрыть, отобразить, переместить геометрическую фигуру. Суть метода переместить остается прежней:
Скрыть;
Задать новое расположение геометрической фигуры;
Отобразить;
Методы скрыть и отобразить для каждой геометрической фигуры будут определять по-своему, поэтому необходимо объявить их виртуальными и абстрактными:
Туре
TPosition = class private
Fx, Fy : integer; public
constructor Create (InitX, InitY: Integer);
function GetX: integer;
function GetY: integer;
destructor Destroy; end;
TGeometricalFigure = class (TPosition) private
Fcolor: word; public
constructor Create(InitX, InitY: integer; InitColor: word);
destructor Destroy;
function GetColor: word;
procedure SetColor(NewColor: word);
procedure Show; virtual; abstract;
procedure Hide; virtual; abstract;
Procedure Move(NewX, NewY: integer); end;
Параллелизм — свойство нескольких абстракций одновременно находиться в активном состоянии, т. е. выполнять некоторые операции.
Есть задачи, в которых автоматические системы должны обрабатывать много событий одновременно. В других случаях потребность в вычислительной мощности превышает ресурсы одного процессора. В каждой из таких ситуаций естественно использовать несколько компьютеров для решения задачи или задействовать многозадачность на многопроцессорном компьютере.
Процесс — это фундаментальная единица действия в системе. Каждая программа имеет по крайней мере один поток управления, параллельная система, имеет много таких потоков: длительность существования одних недолго, а другие живут в течение всего сеанса работы системы. Реальная параллельность достигается только на многопроцессорных системах, а системы с одним процессором имитируют параллельность за счет алгоритмов разделения времени.
Сохраняемость — это способность абстракции существовать во времени, переживая породивший его процесс, и (или) в пространстве, перемещаясь из своего первоначального адресного пространства.
Любой программный объект существует в памяти и живет в течение некоторого времени. Спектр сохраняемости объектов охватывает:
временные объекты, хранящие промежуточные результаты вычисления выражений;
локальные объекты, существующие внутри подпрограмм, время жизни которых исчисляется от вызова подпрограммы до ее завершения;
глобальные объекты, существующие, пока программа загружена в память;
сохраняемые данные, которые сохраняются в файлах внешней памяти между сеансами выполнения программы.
Композиция и наполнение
В результате объектной декомпозиции второго и далее уровней могут получиться объекты, находящиеся между собой в отношении включения. Классы для реализации таких объектов могут строиться двумя способами: с использованием наследования или композиции.
Наследование применяется тогда, когда разрабатываемый класс имеет с исходным сходную структуру и элементы поведения. В тех случаях, когда сходное поведение не просматривается или наследование по каким-то причинам нецелесообразно, можно использовать композицию классов.
Композицией называется такое отношение между классами, когда один является частью второго. Композиция реализуется включением в класс поля, являющегося объектом другого класса. Такие поля называют объектными.
Включение объектов в некоторый класс можно реализовать и с использованием указателей на объекты, что позволяет включить 0 или более объектов (если они собраны в массив или списковую структуру). Такая реализация класса называется наполнением.
пражнение 1.4.1. Приложение «Бильярд». Спроектируйте классы для написания компьютерной игры в бильярд.
Решение
Для реализации объектов потребуются классы: TBilliardTab-1е (бильярдный стол) и TCircle (шар).
Для описания шаров воспользуемся классом TCircle (упр. 1.3.3), дополнив его новыми характеристиками: color (цвет) и dx, dy — шаг смещения:
Туре
TBall = class (TCircle) private
dx, dy : integer; color: word; public
procedure SetColor(NewColor: word); function GetColor: word;
onstrucror Create(InitX, InitY, InitR, Initdx, Initdy: integer; InitColor: word); end;
Бильярдный стол — это прямоугольник, из которого вырезали по сторонам шесть окружностей (луз). На форме располагаются N окружностей (шаров), образующих первоначальную к онфигурацию:
Туре
TCollection = array[1..100] of TBall; TBilliardTable = class private x, y, width, height: word;
{координаты верхнего левого угла и размеры бильярдного стола}
color: word; {цвет стола}
N: byte; {количество шаров}
Balls: TCollection; {массив указателей на шары}
Pockets: array [1.. б] of TCircle; {массив указателей на лузы} procedure SetConfiguration; abstract;
{начальная расстановка шаров} public
constructor Create(InitX, InitY, InitWidth, InitHeight, NewColor: word; InitN: byte); {конструктор определяет размеры бильярдного стола, расположение луз, вызывает процедуры SetConfiguration и Draw для первоначальной расстановки шаров и изображения стола}
procedure Draw; {изображение бильярдного стола}
procedure Test; {проверка шаров на столкновение шаров между собой и со стенками стола, направление движения шаров при этом изменяется, а также проверка на попадание шаров в лузы}
procedure Solve; {выполняет перемещение шаров,
уменьшая скорость движения шаров; вызывает процедуру Test} destructor Destroy; end;
Таким образом, используя механизм наполнения, в класс TBilliardTable включены объектные поля Balls и Pockets
2.4 Отладка и тестирование программного продукта на уровне модулей
Тестирование и отладка идут рука об руку, так что большинство программистов просто не воспринимают их как отдельные этапы разработки программ. Однако путь к успеху лежит через разделение процесса отладки и тестирования на два разных этапа работы над программой, и вам следует четко представлять себе, что цель тестирования — определить наличие (или отсутствие) ошибок, В то время как цель отладки — определить местоположение ошибок и устранить их. Поскольку цели этих двух этапов разработки программ различны, различны и используемые для этого методы и инструменты. Создание надежного приложения
Лучший путь исключить ошибки в программе — защититься от них еще при написании кода. Надежное приложение — приложение, создаваемое с возможностью легко и просто отлаживать его. Вот основные советы, которые помогут уменьшить количество ошибок при разработке программ.
Приложение должно быть хорошо организовано. Разделите программу на модули, каждый из которых выполняет определенные задачи. Например, если код, создающий отчет, разнесен по десяти модулям, время отладки такого кода увеличится даже более чем в десять раз (хотя бы за счет поиска нужной строки в десяти модулях). Конечно же, вы можете вызывать подпрограммы из других модулей, но они должны быть созданы для выполнения четко поставленной задачи. Неверно размещать одну половину выполняемой операции в процедуре в одном модуле, а вторую половину— в другой процедуре (тем более— в другом модуле). Если процедура не может переварить некорректные данные и вызвать тем самым крах всей системы, проверьте целостность входных данных, прежде чем работать с ними. Однако помните: если системой сможет воспользоваться любой дурак, значит, только дурак и будет ею пользоваться. Не увлекайтесь чрезмерной защитой, которая неумолимо будет отбирать время и ресурсы, необходимые для выполнения более важных задач.
Используйте отладочный вариант вашей программы. В отладочной версии программы содержится дополнительный код, цель которого — отследить выполнение программы, убедиться в корректности ее работы и упростить отладку вашего приложения. Именно об этом и рассказывается в следующем подразделе. Отладочная и коммерческая версии кода. Те, кто участвовали в "полевых испытаниях" (известных как бета-тестрирование) коммерческих программ, наверняка обратили внимание, что такие версии программ более медлительны, гораздо более "разговорчивы" и размером побольше окончательных версий программ. Может быть, разработчик спешил и выпустил "сырой" продукт, который будет улучшать перед выпуском окончательного варианта? Так тоже бывает, но главная причина в другом: в бета-версии содержится тестовый и отладочный коды, используемые разработчиком для проверки корректности работы программы.
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 некорректна');
Если же символ Debug не определен при создании коммерческого варианта программы, код вырождается в алгоритм быстрой сортировки без дополнительных проверок
DataSet:= GetData; //Получение данных для сортировки.
Re5ultSet:= Sort_Hare(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, приведенного на рисунке.
![](43049_html_1845f39a.jpg) ![](43049_html_1845f39a.jpg)
Использование диалогового окна 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.
Модульное тестирование
Тема модульного тестирования обширна и многообразна, и писать о ней можно много, но я ограничусь буквально несколькими словами. Кстати, когда речь идет о модульном тестировании, слово модуль не имеет отношения к концепции модулей Delphi и подразумевает функцию, подсистему или другой хорошо определенный программный модуль.
Коротко говоря, идея модульного тестирования состоит в разбивке приложения на функциональные единицы и тестировании каждой из них по отдельности. Это часто означает написание одного или нескольких небольших приложений-оболочек, цель создания которых — отработать один из модулей вашего приложения. Ваша задача — выявить все возможные ошибки, так как сообщения о внутренних ошибках программы, допустимые в тестовых версиях, недопустимы в коммерческих.
Настройка IDE для отладки. Для работы со встроенным отладчиком Delphi его интегрированная среда разработки (IDE) предлагает целую серию установок, большинство из которых вам лучше не трогать, а оставить, как есть (по умолчанию). Однако если вы все-таки решили изменить установки, выберите команду Tools/Options и в появившемся диалоговом окне Environment Options щелкните на вкладке Preferences. Ниже перечислены опции вкладки Preferences и их функции.
Integrated Debugging. Позволяет включать и отключать встроенный отладчик. Если вы отключите отладчик, отладочные команды в меню Run станут недоступными.
![](43049_html_m68b53d90.jpg)
Использование вкладки Preferences для настройки интегрированного отладчика Delphi.
Step Program Block. Эта опция определяет, должен ли отладчик останавливаться перед началом выполнения основного блока begin. . . end при трассировке программы. Обычно данная опция отключена, и включать ее имеет смысл при добавлении кода в основной блок программы либо при отладке консольного приложения.
Hide Designers on Run. Когда эта опция включена, окно Object Inspector и формы, использующиеся при разработке приложения, перед запуском программы на выполнение закрываются. Отключение опции позволяет запускать программу быстрее, но эффект перекрывается используемыми незакрытыми ресурсами приложения. Впрочем, будет ли выбрана эта опция, зависит от пользователя.
Break on Exception. При включенной опции IDE всегда перехватывает исключительные ситуации и выводит окно сообщения, даже если в программе исключительная ситуация обрабатывается блоком try. . .except. Включение этой опции упростит отладку, так как выводимые сообщения при этом будут более информативными, чем сообщения обработчика, установленные по умолчанию (сравните рис. 2.6 и 2.7). Помимо этого, IDE размещает окно редактора поверх остальных и выделяет строку, вызвавшую исключительную ситуацию.
![](43049_html_m8ed57b6.jpg)
Сообщение об исключительной ситуации при включенной опции Break on Exception
Включение в код отладочной информации .
Перед началом отладки следует убедиться, что в приложение включена отладочная информация Delphi.
Для компиляции проекта с отладочной информацией следует выполнить команду Project/Options и в диалоговом окне Project Options выбрать вкладку Compiler.
![](43049_html_m42c96537.jpg)
Окно редактора с отладочными значками
![](43049_html_209a66ad.jpg)
Вкладка 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 предоставляет пользователю несколько команд пошаговой отладки доступных в меню Run (рис 2.10)
![](43049_html_7ba4ce21.jpg)
Используйте меню Run для выполнения команд отладки
Ниже перечислены команды отладки.
Run. Выбор этой команды запускает приложение на выполнение в обычном режиме. Вы можете использовать ее как для запуска приложения, так и для продолжения его работы после какого-либо прерывания выполнения (например, по точке останова). Если включена опция Break on Exception, используйте команду для продолжения работы после получения сообщения об исключительной ситуации
Step Over. Когда точка выполнения находится на строке содержащей вызов процедуры или функции, используйте эту команду для выполнения строки, включая вызовы в один шаг, без прохождения отдельных строк вызываемых функций. Точка выполнения перемещается при выполнении на следующую строку
Trace Into. В отличие от предыдущей команды, эта опция отработает пошаговую отладку вызываемых процедур и функций. Другими словами, если, например, в строке вызывается некая процедура, то при выполнении этой команды точка выполнения перейдет на первую строку процедуры. Однако если в строке нет таких вызовов, значит, последние две команды идентичны. Будьте осторожны при пошаговой трассировке обработчика события OnPaint. Поскольку при пошаговой отладке окно редактора размещается поверх других окон, требуется перерисовка окна приложения, для чего вызывается обработчик события OnPaint... Вы попадаете в замкнутый круг, точнее— в бесконечный цикл вызовов одного и того же обработчика. Тем не менее, стоит лишь проследить, чтобы окна приложения и редактора не перекрывались, и проблема разрешится сама собой.
Trace to Next Source Line. Иногда ваш код вызывает другой код косвенно, например, при вызове функции, которая запускает обработчик события, или при вызове функции Windows API, которая, в свою очередь, запускает функцию косвенного вызова. Поскольку такие вызовы косвенные, отладчик не видит вызова и не отслеживает пошагового выполнения таких вызовов
|