Исключительная ситуация как класс

Обработка исключительных ситуаций

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

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

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

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

Исключительная ситуация как класс

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

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

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

Вы можете получить и обработать этот объект, предусмотрев в программе специальную языковую конструкцию (try..except). Если такая конструкция не предусмотрена, все равно исключение будет обработано — в недрах VCL есть соответствующие обработчики, окружающие все потенциально опасные места.

Чем же различаются между собой исключительные ситуации? Как отличить одну исключительную ситуацию от другой? Поскольку это объекты, они отличаются классом (объектным типом). В модуле SYSUTILS.PAS описан объектный тип Exception. Он является предком для всех других объектов — исключительных ситуаций. Вот он:

Exception = class(TObject)

private

FMessage: string;

FHelpContext: Integer;

public

constructor Create(const Msg: string);

constructor CreateEmt(const Msg: string; const Args: array of const);

constructor CreateRes(Ident: Integer); overload;

constructor CreateRes(ResStringRec: PResStringRec); overload;

constructor CreateResFmt(Ident: Integer; const Args: array of const);

overload; constructor CreateResFmt(ResStringRec: PResStringRec; const Args: array of const);

overload;

constructor CreateHelp(const Msg: string; AHelpContext: Integer);

constructor CreateFmtHelp(const Msg: string; const Args:

array of const;

AHelpContext: Integer);

constructor CreateResHelp(Ident: Integer; AHelpContext: Integer);

overload;

constructor CreateResHelp(ResStringRec: PResStringRec; AHelpContext: Integer); overload;

constructor CreateResFmtHelp(ResStringRec: PResStringRec; const Args: array of const; AHelpContext: Integer); overload;

constructor CreateResFmtHelp(Ident: Integer; const Args: array of const; AHelpContext: Integer); overload;

property HelpContext: Integer read FHelpContext write FHelpContext;

property Message: string read FMessage write FMessage;

end;

ExceptClass = class of Exception;

Как видно из приведенного описания класса Exception, у него имеется двенадцать (!) конструкторов, позволяющих задействовать при создании объекта текстовые строки из ресурсов приложения (имя включает строку Res), форматирование текста (включает Fmt), связь с контекстом справочной системы (включает Help).

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

If MemSize > Limit then

raise EOutOfMemory.CreateFmt('Cannot allocate more than %d

bytes',[Limit]);

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

И наконец, если в названии фигурирует подстрока Help, то такой конструктор инициализирует свойство HelpContext создаваемого объекта. Естественно, система помощи должна быть создана и в ней должна иметься статья, связанная с этим контекстом. Теперь пользователь может затребовать помощь для данной ситуации, скажем, нажав клавишу в момент показа сообщения об ИС.

Тип Exception порождает многочисленные дочерние типы, соответствующие часто встречающимся случаям ошибок ввода/вывода, распределения памяти и т. п. Дерево исключительных ситуаций Delphi 7 приведено на рис. 3.1.

Заметим, что тип Exception и его потомки представляют собой исключение из правила, предписывающего все объектные типы именовать с буквы Т.

Потомки Exception начинаются с Е, например EZeroDivide.

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

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

Рис. 3.1. Дерево объектов исключительных ситуаций Delphi 7

Таблица 3.1. Исключительные ситуации при работе с памятью (порожденыотEHeapException)

Тип Условие возникновения
EOutOfMemory Недостаточно места в куче (памяти)
EOutOfResources Нехватка системных ресурсов
EInvalidPointer Недопустимый указатель (обычно nil)

Таблица 3.2. Исключительные ситуации целочисленной математики (порождены отEIntError)

Тип Условие возникновения
EDivByZero Попытка деления на ноль (целое число)
ERangeError Число или выражение выходит за допустимый диапазон
EIntOverflow Целочисленное переполнение

Таблица 3.3. Исключительные ситуации математики с плавающей точкой (порождены от EMa thError)

Тип Условие возникновения
EInvalidOp Неверная операция
EZeroDivide Попытка деления на ноль
EOverflow Переполнение с плавающей точкой
EUnderflow Исчезновение порядка
EInvalidArgument Неверный аргумент математических функций

Для этого используется оператор raise, за которым в качестве параметра должен идти экземпляр объекта типа Exception. Обычно сразу за оператором следует конструктор класса ИС:

raise EMathError.Create(' ');

но можно и разделить создание и возбуждение исключительной ситуации:

var E: EMathError;

begin

E:= EMathError.Create С');

raise E;

end;

Оператор raise передает созданную исключительную ситуацию ближайшему блоку try..except (см. ниже).

if С = 0 then

raise EDivByZero.Create('Деление на ноль')

else

А:= В/С;

Самостоятельная инициализация ИС может пригодиться при программировании реакции приложения на ввод данных, для контроля значений переменных и т. д. В таких случаях желательно создавать собственные классы ИС, специально приспособленные для ваших нужд. Также полезно использовать специально спроектированные исключительные ситуации при создании собственных объектов и компонентов. Так, многие важнейшие классы VCL — списки, потоки, графические объекты — сигнализируют о своих (или ваших?) проблемах созданием соответствующей ИС — EListError, EInvalidGraphic, EPrinter и т. д.

Самый важный отличительный признак объекта Exception — это все же класс, к которому он принадлежит. Именно факт принадлежности возникшей ИС к тому или иному классу говорит о том, что случилось. Если же нужно детализировать проблему, можно присвоить значение свойству Message. Если и этого мало, можно добавить в объект новые поля. Так, в ИС EinOutError (ошибка ввода/вывода) есть поле ErrorCode, значение которого соответствует произошедшей ошибке — запрету записи, отсутствию или повреждению файла и т. д.

try

.FileOpenС с:\myfile.txt', fmOpenWrite);

except

on E: EinOutError do

case E.ErrorCode of

ERROR_FILE_NOT_FOUND {=2}: ShowMessage('Файл не найден!');

ERROR_ACCESS_DENIED {=5}: ShowMessage('Доступ запрещен!');

ERROR_DISK_FULL {=112}: ShowMessage ('Диск переполнен!');

end;

end;

Впрочем, ИС EInOutError возникают только тогда, когда установлена опция компилятора {$IOCHECKS ON} (или иначе {$I+}). В противном случае проверку переменной IOResult (известной еще по Turbo Pascal) нужно делать самому.

Еще более "продвинутый" пример — ИС EDBEngineError. Она имеет свойства ErrorCount и свойство-массив Errors: одна операция с базой данных может породить сразу несколько ошибок.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: