Ракитин Р.Ю. ООП в Turbo Delphi
.PDF71
Глава 5. Понятие исключительной ситуации и её обработка
Успешное завершение процесса компиляции не означает, что в программе нет ошибок. Убедиться, что программа работает правильно можно только в процессе проверки ее работоспособности, который называется тестирование.
Обычно программа редко сразу начинает работать так, как надо, или
работает правильно только на некотором ограниченном наборе исходных данных. Это свидетельствует о том, что в программе есть логические ошибки. Процесс поиска и устранение ошибок называется отладкой.
Классификация ошибок
Ошибки, которые могут быть в программе, принято делить на три группы:
∙синтаксические;
∙ошибки времени выполнения;
∙логические.
Синтаксические ошибки, их также называют ошибками времени компиляции (Compile-time error), наиболее легко устранимы. Их обнаруживает компилятор. Сообщения о найденных ошибках отображаются в нижней части программы в окне Сообщения / Создание (Messages / Build). Программисту
остается только внести изменения в текст программы и выполнить повторную компиляцию.
Ошибки времени выполнения, в Delphi они называются исключениями
(exception), тоже, как правило, легко устранимы. Они обычно проявляются уже при первых запусках программы и во время тестирования. Ошибки времени выполнения связаны с возникновением исключительных ситуаций, которые заключаются в изменении условий выполнения программы, которые приводят к прерыванию или прекращению выполнения приложения.
Они являются результатом неправильной работы функций, процедур или методов из библиотеки подпрограмм (Run-Time Library – RTL), библиотеки визуальных компонентов (Visual Component Library – VCL) или даже операционной системы (Operating System – OS).
Некоторые из возможных источников перечислены ниже.
1.Компоненты, включенные в программу.
2.Ошибки ввода.
3.Драйверы баз данных.
4.Операционная система.
5.Драйверы устройств, например видеодрайвер.
6.Сетевые динамические библиотеки, системы управления и антивирусные программы.
7.Собственные библиотеки RTL или VCL Delphi.
+Следует предвидеть ошибки, которые могут возникать при выполнении программы, и программировать их обработку.
72
При возникновении ошибки в программе, запущенной из Delphi, среда разработки прерывает работу программы, о чем свидетельствует заключенное в скобки слово Stopped в заголовке главного окна Delphi, и на экране появляется диалоговое окно, которое содержит сообщение об ошибке и информацию о типе (классе) ошибки. На рис. приведен пример сообщения об ошибке, возникающей при попытке открыть несуществующий файл.
После возникновения ошибки программист может либо прервать выполнение программы, для этого надо из меню Run выбрать команду Program Reset, либо продолжить ее выполнение, например, по шагам, наблюдая результат выполнения каждой инструкции.
С логическими ошибками дело обстоит иначе. Компиляция программы, в которой есть такая ошибка, завершается успешно и программа ведет себя нормально. Однако при анализе результата работы программы выясняется, что результат неверный. За наиболее часто встречающимися ошибками можно заставить следить саму программу. Для этого в настройках проекта Project → Options (Проект → Настройки) на вкладке Compiler (Компилятор) надо выполнить следующие действия:
∙ На панели Code generation (Генерация машинного кода) сбросьте флажок Optimization (Оптимизация). Когда компилятор создает
73
оптимизированный код, он нередко вносит существенные улучшения
вдетали алгоритма, реализуемого в Delphi.
∙На панели Runtime errors (Ошибки времени выполнения) должны быть установлены флажки Range checking (Контроль выхода индекса за границы массива), I/O Checking (Контроль ошибок ввода/вывода) и Overflow checking (Контроль переполнения при целочисленных операциях).
∙На панели Debugging (Отладка) установите флажки Debug information (Добавление отладочной информации), Local symbols (Просмотр значений локальных переменных), Reference Info (Просмотр структуры кода), Assertions (Включение процедуры Assert
вмашинный код) и Use Debug DCUs (Использование отладочных версий стандартных модулей библиотеки компонентов VCL). Без указания данных параметров успешная отладка в Delphi будет невозможна.
Для выявления более сложных логических ошибок программист анализирует алгоритм, вручную «прокручивая» его выполнение.
Обработка исключительных ситуаций
Впрограмме во время ее работы могут возникать ошибки, причиной которых, как правило, являются действия пользователя. Например, пользователь может ввести неверные данные или, что бывает довольно часто, удалить нужный программе файл. При этом возникает нарушение в работе программы, которое, как было сказано выше, называется исключением.
Обработку исключений (ошибок) берет на себя автоматически добавляемый в выполняемую программу код, который обеспечивает в том числе вывод информационного сообщения. Вместе с тем Delphi дает возможность программе, а следовательно, и программисту выполнить обработку возникшего исключения. Когда возникает исключительная ситуация, независимо от ее источника, программа узнает о ней. Поэтому можно использовать эту возможность и, определив места возможного возникновения ошибок, внести специальный код для их обработки.
ВDelphi определены две конструкции для обработки локальных исключительных ситуаций: try…except и try…finally, а так же процедура
Assert.
+ Помимо специальных конструкций можно воспользоваться и
другими способами обработки исключений. Например, при нахождении частного можно с помощью оператора ветвления, проверять делитель на равенство нулю.
74
1. Процедура Assert
Данная процедура используется для отладки программ. Её польза заключается в том, что записывается компактно и позволяет добавлять
промежуточную проверку принадлежности значений переменных допустимым диапазонам в любое место программы.
procedure Assert(Condition: Boolean {; Message: String});
У этой процедуры два параметра: первый – логическое выражение Condition, второй – строка сообщения Message. Если значение выражения равно True, то ничего не происходит, в противном случае возникает искусственно сгенерированная ошибочная ситуация, тогда пользователю выдается сообщение о месте, где расположена данная процедура (имя файла, номер строки) и информационное сообщение (второй параметр). Например:
Assert(I in [2..9], ‘Счетчик вне диапазона’); Assert(N<>0, ‘Делитель не может быть равен нулю’);
Подобные проверки желательно проводить в исходные тексты как можно чаще, чтобы контролировать правильность процесса вычислений. Кроме того в конечной версии приложения, где наличие подобных проверок не требуется, удалять вызовы процедуры Assert из текста не обязательно. Достаточно в коде программы либо указать директиву компилятору {$C-}, либо отключить флажок Assertions (Включение процедуры Assert в машинный код) в
настройках проекта (см. выше).
2. Конструкция try…finally
try
{код программы с возможной ошибкой выполнения} finally
{блок операторов выполняющийся в любом случае} end;
Данная конструкция используется, когда нет необходимости в обработчики событий, а необходимо гарантированно выполнить определенную последовательность действий.
Если в любом из операторов, размешенных между try и finally, возникает исключительная ситуация, выполнение передается первому оператору, следующему за командой finally, после чего выполняются все операторы до заключительного end.
Назначение конструкции try…finally. Конструкция try…finally применяется для довольно длительного перехвата исключительной ситуации, чтобы выполнить все полагающиеся перед завершением программы действия,
прежде чем управление будет передано глобальному обработчику исключительных ситуаций или следующему уровню обработки ошибки. Эта
конструкция не удаляет экземпляра объекта обработки исключительной ситуации и, таким образом, не обрабатывает ошибки.
75
В следующем примере показан способ освобождения памяти, выделенной для динамического массива DynArr (финальный оператор присваивания nil). Который произойдет независимо от того, успешно ли создан и обработан массив DynArr, или памяти для него не хватило.
var DynArr : array of integer;
...
try
SetLength (DynArr, 100000);
...
finally
DynArr := nil end;
3.Конструкция try…except
Вотличие от конструкции try…finally конструкция try…except позволяет обрабатывать исключительные ситуации. Вид конструкции представлен ниже:
try
{код программы с возможной ошибкой выполнения} except
{операторы выполняющиеся в случае возникновения исключительной ситуации}
else
{операторы выполняющиеся в случае возникновения не известной исключительной ситуации}
end;
Ключевое слово try (попытка) обозначает начало блока контроля выполнения операторов, следующих до ключевого слова except. В случае
возникновения исключительной ситуации происходит обращение к списку классов, перечисленных перед завершающим словом end. При этом выполняется действие, указанное для соответствующего класса, а затем управление передается первому оператору, следующему за завершающим словом end. Операторы, оставшиеся в части try, пропускаются. Если исключительной ситуации не встретилось, то пропускаются все действия, следующие за ключевым словом except.
Если возникшая ситуация не относится ни к одному обрабатываемому классу, то выполняется команды, указанная после ключевого слова else (необязательная часть).
Назначение конструкции try…except. Конструкция try…except
применяется для перехвата исключительной ситуации с последующей возможной обработкой, предусматривающей освобождение экземпляра обработки исключительной ситуации, после чего выполнение программы продолжается как обычно. Рассмотрим пример:
76
procedure TForml.ButtonlClick(Sender: TObject); var
iCtr: integer; begin
for iCtr := Ord('A') to Ord('Z') do try
ChDir(Char(iCtr)+':\DUKE3D'); MessageDlg('\DUKE3D найдена на диске '+ Char(iCtr), mtlnformation, [mbOK], 0);
Break; except
MessageDlg('\DUKE3D не найдена на диске '+ Char(iCtr), mtError, [mbOK], 0);
end; end;
В данном примере для перебора всех возможных имен дисков от А до Z применяется конструкция for…do. В каждом цикле происходит обращение к процедуре ChDir из RTL с параметром, содержащим имена очередного диска и каталога «DUKE 3D». Если данный каталог не найден на очередном диске, то
при выполнении процедуры ChDir возникает исключительная ситуация типа EInOutError. Выполнение передается первому оператору, следующему за командой except. В данном примере это приводит к выводу сообщения о том, что на текущем диске нет заданного каталога. Если в процедуре ChDir возникла ошибка, следующие за ней операторы вызова процедур MessageDlg и Break не выполняются. Если же каталог найден, то его выполнение приводит
квыходу из цикла и нормальному возврату из метода.
4.Конструкция on…do
Вконструкции try…except содержится необязательный элемент, с
помощью которого можно точно определить тип исключительной ситуации и узнать адрес образца объекта его обработки, получив тем самым доступ к членам, объявленным как public и published. Формат этого элемента:
on <название класса> do <операторы>.
Конструкция on…do допустима только между командой except и заключительной end, как в следующем примере.
try
{Операторы} except
on E: Exception do
{Оператор, возможно, составной} else
{Оператор, возможно, составной} end;
77
Конструкция on…do применяется в случаях, когда дальнейшие действия зависят от типа исключительной ситуации. Кроме того, ее можно использовать
для инициализации неожиданных исключительных ситуаций при обработке ожидаемых. Рассмотрим следующий пример.
try
Assert(y<>5,’’); x := 100 div y;
except
on EZeroDivide do ZeroProc; on EAssertioFailed do
begin
ShowMessage (‘Ошибка #22’); x:=0;
end;
else ShowMessage(‘Непонятно что’); end;
Если в операторе присваивания обнаружена попытка деления на ноль (y=0), то выполнится процедура ZeroProc (определена заранее). Если же значение y окажется равным 5 (функция Assert), то обработчиком исключительных ситуаций будет сгенерирован объект класса EAssertionFailed и выполнится группа операторов в логических скобках. Оператор x:=100 div y при этом будут пропущен. Если встретится какая-то другая исключительная ситуация, то выведется соответствующее сообщение.
Ниже представлена таблица основных классов исключительных ситуаций.
Это исключение |
Возбуждается когда ... |
EAbort |
Необходимо просто прекратить текущий блок кода. |
EAccessViolation |
Делается попытка получения доступа к недействительному |
|
разделу памяти. |
EAssertionFailed |
Значение выражения в процедуре Assert равно False |
|
|
EBitsError |
Свойство Bits объекта TBits проиндексировано неправильно. |
EControlC |
В консоли приложения нажато <Ctrl>+<C>. |
EConvertError |
StrToInt или StrToFile пытаются выполнить преобразование |
|
недействительного значения. |
EDivByZero |
Попытка деления целого на нуль. |
EExternal |
Неверное функционирование системы Windows. |
EFCreateError |
Ошибка во время создания файла. |
EInOutError |
Обнаружена ошибка любого типа файлового ввода-вывода. |
EIntError |
Базовый класс, на основе которого созданы классы |
|
исключительных ситуаций при работе с целыми числами. |
EIntOverflow |
Присвоение целого слишком велико для регистра. |
EInvalidCast |
Попытка неправильного приведения типов посредством as. |
EInvalidOp |
Произошла одна из множества ошибок с плавающей точкой. |
EListError |
Ошибка произошла при работе со списком, строкой или |
|
объектом списка строк. |
78
EMathError |
Базовый класс, на основе которого созданы классы |
|
исключительных ситуаций при работе с целыми числами. |
EMenuError |
Произошла ошибка меню. |
EOverflow |
Присвоение с плавающей точкой слишком велико для регистра. |
EOutOfMemory |
Нехватка памяти. |
EPrinter |
Произошла ошибка печати. |
EPropReadOnly |
Попытка записи в свойство, предназначенное только для чтения. |
EPropWriteOnly |
Попытка чтения свойства, предназначенного только для записи. |
ERangeError |
Попытка присвоить целому значение, выходящее за пределы |
|
диапазона. |
EStringListError |
Ссылка на элемент списка вне диапазона списка. |
EUnderflow |
Присваивание с плавающей точкой слишком мало и |
|
устанавливается на 0. |
EVariantError |
Произошла ошибка с типом variant. |
|
|
EZeroDivide |
Попытка деления на нуль числа с плавающей точкой. |
5.Преднамеренная генерация исключений. Оператор raise
Спомощью ключевого слова raise можно сгенерировать исключение любого типа в любом месте программы. Синтаксис оператора генерации исключения следующий:
raise <исключение>;
Вместо исключения можно указать тип исключения и конструктор raise <тип исключениям>.Create(<текст сообщения>);
Здесь <тип исключения> может быть любым из предопределенных в Delphi, а <текст сообщения> – тем текстом, который будет отображен в диалоговом окне при стандартной (без обработчика on) обработке исключения.
При этом тип исключения и текст сообщения могут быть никак не связаны друг с другом.
Например, если программа читает текст из окна редактирования Edit1, в котором пользователь должен задать какую-то информацию, то проверить, задана ли информация, можно оператором
...
if Editl.Text=’’ then raise
EZeroDivide.Create('He задана требуемая информация');
...
В результате, если пользователь не задал необходимой информации, расчет прервется и будет сгенерировано исключение типа EZeroDivide (якобы целочисленное деление на нуль). Если это исключение не перехватывается в программе никаким обработчиком, то пользователь увидит диалоговое окно с сообщением об ошибке.
79
Таким образом, можно использовать предопределенные в Delphi исключения для информации об ошибках, не имеющих никакого отношения к их первоначальному смыслу. Конечно, для таких целей лучше использовать не EZeroDivide, а какое-нибудь более экзотическое исключение, обработчики которого заведомо не присутствуют в программе.
Отладчик
Интегрированная среда разработки Delphi предоставляет программисту мощное средство поиска и устранения ошибок в программе – отладчик. Отладчик позволяет выполнять трассировку программы, наблюдать значения переменных, контролировать выводимые программой данные.
1. Трассировка программы
Во время работы программы ее инструкции выполняются одна за другой со скоростью работы процессора компьютера. При этом программист не может определить, какая инструкция выполняется в данный момент, и, следовательно, определить, соответствует ли реальный порядок выполнения инструкций разработанному им алгоритму.
В случае неправильной работы программы необходимо видеть реальный порядок выполнения инструкций. Это можно сделать, выполнив трассировку программы. Трассировка – это процесс выполнения программы по шагам (step- by-step), инструкция за инструкцией. Во время трассировки программист дает команду: выполнить очередную инструкцию программы.
Delphi обеспечивает два режима трассировки: без захода в процедуру (Step over) и с заходом в процедуру (Trace into). Режим трассировки без захода в процедуру производит трассировку только главной процедуры, при этом трассировка подпрограмм не выполняется, вся подпрограмма выполняется за один шаг. В режиме трассировки с заходом в процедуру производится трассировка всей программы, то есть по шагам выполняется не только главная программа, но и все подпрограммы.
Для того чтобы начать трассировку, необходимо в меню выбрать Run → Step over (Запуск → Пошагово) (клавиша F8) или Run → Trace into
(Запуск → Пошагово с заходом в процедуру) (клавиша F7). В результате в окне редактора кода будет выделена первая инструкция программы. Для выполнения следующей инструкции повторите команду.
В любой момент времени можно завершить трассировку и продолжить выполнение программы в реальном темпе. Для этого надо из меню выбрать
Run → Run (клавиша F9).
При необходимости выполнить трассировку части программы следует установить курсор на инструкцию программы, с которой надо начать трассировку, и из меню выбрать Run → Run to cursor (Запустить → Запустить от курсора) или нажать клавишу F4. Затем, нажимая клавишу F7 или клавишу F8, выполнить трассировку нужного фрагмента программы.
80
Во время трассировки можно наблюдать не только порядок выполнения инструкций программы, но и значения переменных. О том, как это сделать, рассказывается ниже.
2. Точки останова программы
При отладке широко используется метод, который называют методом точек останова. Суть метода заключается в том, что программист помечает некоторые инструкции программы (ставит точки останова), при достижении которых программа приостанавливает свою работу, и программист может начать трассировку или проконтролировать значения переменных.
Для того чтобы поставить в программу точку останова (breakpoint), необходимо щелкнуть мышью на синей точке, помечающей ту инструкцию программы, перед которой надо поместить точку останова (если в программе нет ошибок, то компилятор помечает выполняемые инструкции программы синими точками). Для точки останова можно задать дополнительные параметры, для этого выберите из меню Run → Add Breakpoint (Добавить точку останова), затем из меню следующего уровня – команду Source Breakpoint.
В результате открывается диалоговое окно Add Source Breakpoint, в котором
выводится информация о добавляемой точке останова. Поле Filename содержит имя файла программы, куда добавляется точка останова, поле Line number – номер строки программы, в которую добавляется точка останова. Условие, при выполнении
которого программа приостановит свою работу в данной точке, вводится в поле
Condition. Количество пропусков данной точки останова можно ввести в поле
Pass count (Число пропусков).
Для того чтобы удалить точку останова, щелкните в окне редактора кода мышью на красной точке, помечающей строку, в которой находится точка останова.
3. Наблюдение значений переменных
Во время отладки, при выполнении программы по шагам,
довольно часто бывает полезно знать, чему равно значение той или иной переменной. Отладчик
позволяет наблюдать значения переменных программы.
Для того чтобы во время выполнения программы по шагам иметь возможность контролировать