«Ни один ремесленник, который стремится к вершинам своей профессии, не примет негодных инструментов; и ни один производитель, который ценит качество работы, не будет упрашивать ремесленника принять их»
32. Примеры обработки исключительных ситуаций
Ниже приведены процедуры A, B и C, обсуждавшиеся ранее, воплощенные в новом синтаксисе Object Pascal:
{$APPTYPE CONSOLE}
Program Excep_1; {Иллюстрация обработки исключительных ситуаций.
Программа построена на основе заимствованного примера
с добавлением изменений.
Автор Овсянник В.Н. }
uses SysUtils;
type ESampleError = class(Exception);
var
ErrorCondition: Boolean;
i : byte;
procedure C;
begin
writeln('Enter C');
write('Input byte value>');
ReadLn(i);
i:=20 div i; {Здесь возникнет ИС при вводе i=0}
// любые другие операторы
if ErrorCondition then
begin {Создаем самостоятельно ИС}
writeln('Raising exception in C');
raise ESampleError.Create('Error in procedure C!');
end;
writeln('Exit C');
end;
procedure B;
begin
writeln('enter B');
C;
writeln('exit B');
end;
procedure A;
begin
writeln('Enter A');
try
writeln('Enter A''s try block');
B;
writeln('After B call');
except
// а если добавить какой-нибудь оператор сюда, то когда он будет выполнен?
on ESampleError do
writeln('Inside A''s ESampleError handler');
on EIntError do
writeln('Inside A''s EIntError handler');
end;
writeln('Exit A');
end;
begin
writeln('begin main');
ErrorCondition := false;
A;
writeln('end main');
ReadLn;
end.
При ErrorCondition = True и вводе ненулевого значения i программа выдаст:
begin main
Enter A
Enter A's try block
enter B
Enter C
Input byte value>12
Raising exception in C
Inside A's ESampleError handler
Exit A
end main
При ErrorCondition = True и вводе нулевого значения i программа выдаст:
begin main
Enter A
Enter A's try block
enter B
Enter C
Input byte value>0
Raising exception in C
Inside A's EIntError handler
Exit A
end main
Процедура C проверяет наличие ошибки (в примере это значение глобальной переменной) и, если она есть, C вызывает (raise) исключительную ситуацию (ИС) класса ESampleError, т.е. создает экземпляр этого класса и передает ему необходимую информацию. При возникновении ИС или ее создании с помощью raise в момент выполнения программы будет выведено окно с сообщением об ошибке, но работа приложения не будет завершена. Для того чтобы избежать появления окна, надо (в Delphi5) в меню "Tools/Debugger Options/Language Exceptions" снять флажок "Stop on Delphi exeptions".
Процедура A помещает часть кода в блок try ... except. Первая часть этого блока содержит часть кода, аналогично конструкции begin … end. Эта часть кода завершается зарезервированным словом except, далее следует один или более обработчиков исключительных ситуаций on x do y, далее может быть включен необязательный блок else, вся конструкция заканчивается end. В конструкции, назначающей определенную обработку для конкретной исключительной ситуации (on x do y), после зарезервированного слова on указывается класс исключительной ситуации, а после do следует собственно код обработки данной ошибки. Код обработки ошибочной ситуации может быть одиночным оператором или составным. Если возникшая ИС подходит по типу к указанному после on, то выполнение программы переходит сюда (на код после do). Исключительная ситуация подходит в том случае, если она того же класса, что указан в on, либо является его потомком. Например, в случае on EFileNotFound обрабатываться будет ситуация, когда файл не найден. А в случае on EFileIO - все ошибки при работе с файлами, в том числе и предыдущая ситуация. В блоке else обрабатываются все ошибки, не обработанные до этого.
Приведенные в примере процедуры содержат код (строка с writeln), который отображает путь выполнения программы. Когда C вызывает exception, программа сразу переходит на обработчик ошибок в процедуре A, игнорируя оставшуюся часть кода в процедурах B и C.
После того, как найден подходящий обработчик ошибки, поиск заканчивается. После выполнения кода обработчика, программа продолжает выполняться с оператора, стоящего после слова end блока try ... except (в примере - writeln('Exit A')).
Если между словами except и end не указана ни одна ИС, т.е. нет консnрукций on … do, то приведенный здесь код обрабатывает все ИС:
try
. . .
except
UniversalExceptionHandler;
end;
Если же указан хотя бы один оператор on, то между try и except не должно быть никаких других операторов, что поясняет следующий фрагмент кода:
try
. . .
except
// оператор_1– ошибка
on EIntErr do
begin
// реакция на ИС при выполнении целочисленных операций
end;
// оператор_2– ошибка
end;
Конструкция try … except подходит, если известно, какой тип ошибок нужно обрабатывать в конкретной ситуации. Но что делать, если требуется выполнить некоторые действия в любом случае, произошла ошибка или нет? Это тот случай, когда понадобится конструкция try … finally.
Рассмотрим модифицированную процедуру B:
procedure NewB;
var
P: Pointer;
begin
writeln('enter B');
GetMem(P, 1000);
C;
FreeMem(P, 1000);
writeln('exit B');
end;
Если C вызывает ИС, то программа уже не возвращается в процедуру B. А что же с теми 1000 байтами памяти, захваченными в B? Строка FreeMem(P,1000) не выполнится и Вы потеряете кусок памяти. Как это исправить? Нужно ненавязчиво включить процедуру B в процесс, например:
procedure NewB;
var
P: Pointer;
begin
writeln('enter NewB');
GetMem(P, 1000);
try
writeln('enter NewB''s try block');
C;
writeln('end of NewB''s try block');
finally
writeln('inside NewB''s finally block');
FreeMem(P, 1000);
end;
writeln('exit NewB');
end;
Если в A поместить вызов NewB вместо B, то программа выведет сообщения следующим образом:
begin main
Enter A
Enter A's try block
enter NewB
enter NewB's try block
Enter C
Raising exception in C
inside NewB's finally block
Inside A's ESampleError handler
Exit A
end main
Код в блоке finally выполнится при любой ошибке, возникшей в соответствующем блоке try. Он же выполнится и в том случае, если ошибки не возникло. В любом случае память будет освобождена. Если возникла ошибка, то сначала выполняется блок finally, затем начинается поиск подходящего обработчика. В штатной ситуации после блока finally программа переходит на следующее предложение после блока.
Почему вызов GetMem не помещен внутрь блока try? Этот вызов может окончиться неудачно и вызвать ИС EOutOfMemory. Если это произошло, то FreeMem попытается освободить память, которая не была распределена. Когда мы размещаем GetMem вне защищаемого участка, то предполагаем, что B сможет получить нужное количество памяти, а если нет, то более верхняя процедура получит уведомление EOutOfMemory.
А что, если требуется в B распределить 4 области памяти по схеме "все или ничего"? Если первые две попытки удались, а третья провалилась, то как освободить захваченную область память? Можно так:
Procedure NewB;
var
p,q,r,s: Pointer;
Begin
P := nil;
Q := nil;
R := nil;
S := nil;
try
GetMem(P, 1000);
GetMem(Q, 1000);
GetMem(R, 1000);
GetMem(S, 1000);
C;
finally
if P <> nil then FreeMem(P, 1000);
if Q <> nil then FreeMem(Q, 1000);
if R <> nil then FreeMem(R, 1000);
if S <> nil then FreeMem(S, 1000);
end;
End;
Установив сначала указатели в nil можно определить, успешно ли прошел вызов GetMem.
Оба типа конструкции try можно использовать в любом месте, допускается вложенность любой глубины. ИС можно вызывать внутри обработчика ошибки, конструкцию try можно использовать внутри обработчика ИС.
33. Дополнительные возможности обработки ИС