- •Обработка исключительных ситуаций
- •Исключительная ситуация как класс
- •Защитные конструкции языка Object Pascal
- •Блок try..Except
- •Блок try..Finally
- •Использование исключительных ситуаций
- •Протоколирование исключительных ситуаций
- •Коды ошибок в исключительных ситуациях
- •Функция Assert
- •Исключительная ситуация eAbort
Протоколирование исключительных ситуаций
Часто нужно иметь подробный материал для анализа причин возникновения ИС. Разумно было бы записывать все данные о них в файл, чтобы потом прогнозировать ситуацию. Такой подход важен для программ, которые так или иначе будут отчуждены от разработчика: в случае возникновения непредвиденной ситуации это позволит ответить на вопросы "кто виноват?" и "что делать?". В следующем примере предложен вариант реализации протоколирования ИС.
const LogName : string = 'c:\appexc.log';
procedure LogException;
var fs: TFileStream; m : word;buf : array[0..511] of char;
begin
if FileExists(LogName) then m := fmOpenReadWrite else m := fmCreate;
fs := TFileStream.Create(LogName,m);
fs.Seek(0,soFromEnd);
StrPCopy(Buf,DateTimeToStr(Mow)+' . ');
ExceptionErrorMessage
(ExceptObject,ExceptAddr,@buf[StrLenfbuf)],
SizeOf(Buf)-StrLen(buf));
StrCat(Buf,#13#10);
fs.WriteBuffer (Buf, StrLer. ;buf) ) ;
fs.Free;
end;
procedure TForml.ButtonlClick(Sender: TObject);
var x,y,z: real;
begin
try
try
x:=1.0;y:=0.0;
z := x/y;
except
LogException;
raise;
end;
except
on E:EIntError do ShowMessage('IntError');
on E:EMathError do ShowMessage('MathError');
end;
end;
Здесь задачу записи информации об ИС решает процедура LogException. Она открывает файловый поток и пишет туда информацию, отформатированную При помощи уже упоминавшейся функции ExceptionErrorMessage.
В качестве ее параметров выступают значения функций Exceptobject и ExceptAddr. К сформированной строке добавляется время возникновения ИС. Для каждого защищаемого блока кода создаются две вложенные конструкции try. .except. Первая, внутренняя — для вас; в ней ИС протоколируется и продвигается дальше. Внешняя — для пользователя; именно в ней проводится анализ типа ИС и готовится сообщение.
В Object Pascal существует и расширенный вариант употребления оператора
raise:
raise экземпляр объекта типа Exception> [at <адрес>]
Естественно, объектный тип должен быть порожден от Exception. To, что в таком типе ничего не переопределено, не столь важно — главное, что в обработчике ИС можно отследить именно этот тип.
ELoginError = class (Exception);
If LoginAttemptsNo > MaxAttempts then raise ELoginError.Create('Ошибка регистрации пользователя');
Конструкция at <адрес> используется для того, чтобы изменить адрес, к которому привязывается возникшая ИС, в пределах одного блока обработки ИС.
Коды ошибок в исключительных ситуациях
Если ваше приложение уже готовится к продаже, если вы планируете его техническую поддержку, то пора задуматься о присвоении числовых кодов
Ошибкам, возникающим в нем. Сообщение типа "Exception EZeroDivide in module MyNiceProgram at addr $0781BABO" годится для разработчика, пользователя же оно повергнет в полный ступор. Если он позвонит в вашу службу техподдержки, то, скорее всего, не сможет ничего объяснить. Гораздо грамотнее дать ему уже "разжеванную" информацию и, в том числе, числовой код.
Один из путей решения этой проблемы — размещение сообщений об ошибках в ресурсах программы. Если же вы еще делаете и несколько национальных версий программы на разных языках, то этот путь — единственный.
"Классический" способ поместить текст в файл ресурсов — 3-этапный:
1. Создается исходный файл ресурсов с расширением гс, в который помещаются необходимые строки с нужными номерами.
2. Файл обрабатывается компилятором ресурсов brcc32.exe (находится в папке bin в структуре папок Delphi). На выходе образуется одноименный файл с расширением res.
3. Файл включается в программу указанием директивы $R, например
{$R mystrings.res}.
Чтобы совместно использовать константы-номера ошибок в файле ресурсов и в коде на Delphi, вынесем их в отдельный включаемый файл с расширением inc:
const
IOError = 1000;
FileOpenError = IOError + 1;
FileSaveError = IOError + 2;
InternetError = 2000;
NoConnecticnError = InternetError + 1;
ConnectionAbortedError = InternetError + 2;
Взглянув на файл, вы увидите, что ошибки в нем сгруппированы по категориям. Советуем вам поступить так же, разделив константы категорий промежутком в 1000 или даже 10 000.
Сам файл ресурсов может выглядеть так:
#include "strids.inc" STRINGTABLE
{
FileOpenError, "File Open Error"
FileSaveError, "File Save Error"
NoConnectionError, "No Connection"
ConnectionAbortedError, "Connection Aborted"
}
"Вытащить" строку из ресурсов можно несколькими способами, но самый простой из них — просто по числовому идентификатору, переданному в функцию Loadstr (модуль SysUtils). Код
ShowMessage(LoadStr(NoConnectionError) ) ;
покажет сообщение "NO connection".
Если же строка используется при возбуждении ИС, то место идентификатору—в перекрываемом конструкторе Exception.createRes, один из вариантов которого работает подобно функции Loadstr:
if FileOpent'c:\myfile.txt", fmOpenRead) = INVALID_HANDLE_VALUE then
raise EMyException.CreateRes(FileOpenError) ;
Таким образом, решена половина проблемы: возможным исключительным ситуациям присвоены номера, им в соответствие поставлен текст. Теперь о второй половине — как в обработчике ИС этот номер использовать.
Ясно, что нужно объявить свой класс ИС, включающий в себя свойство-код
ошибки.
EExceptionWithCode = class(Exception)
private
FErrCode : Integer;
public
constructor CreateResCode(ResStringRec: PResStringRec);
property ErrCode: Integer read FErrCode write FErrCode;
end;
Тогда любой обработчик сможет к нему обратиться:
if E is EExceptionWithCode then
ShowMessage('Error code: ' + IntToStr(EExceptionWithCode(E).ErrCode) +
#13*10
+ 'Error text: ' + E.Message);
Присвоить свойству ErrCode значение можно двумя способами:
1. Добавить к классу ИС еще один конструктор, содержащий код в качестве дополнительного параметра:
constructor EExceptionWithCode.CreateResCode(Ident: Integer);
begin
FErrCode := Ident;
inherited CreateRes(Ident);
end;
2. Присвоить значение свойства в промежутке между созданием объекта ИС и его возбуждением:
var E: EExceptionWithCode; begin
E := EExceptionWithCode.CreateRes(NoConnectionError);
E.ErrCode := NoConnectionError;
Raise E;
end;
Вот, казалось бы, последний штрих. Но как быть тем, кто заранее не заготовил файл ресурсов, а работает со строками, описанными в PAS-файлах? Если вы используете оператор resourcestring, то помочь вам можно.
Начнем с рассмотрения ключевого слова resourcestring. Вслед за ним описываются текстовые константы. Но, в отличие от ключевого слова const, эти константы размещаются не в сегменте данных программы, а в ресурсах, и подгружаются оттуда по мере необходимости. Каждая такая константа воспринимается и обрабатывается как обычная строка. Но за каждой из них на самом деле стоит такая структура:
PResStringRec = ^TResStringRec;
TResStringRec = packed record
Module: ^Cardinal;
Identifier: Integer;
end;
Если вы еще раз посмотрите на список конструкторов объекта Exception, вы увидите, что те из них, которые работают с ресурсами, имеют перегружаемую версию с параметром типа pResstringRec. Вы угадали правильно: они — для строк из resourcestring. А взглянув на приведенную выше структуру, вы увидите в ней поле identifier. Это то, что нам надо.
Чтобы у программиста, пользующегося resourcestring, голова не болела об уникальных идентификаторах ресурсных строк, среда Delphi берет на себя заботу об этом. Номера назначаются компилятором, начиная от 65 535 (SmallInt (-D) и ниже (если рассматривать номер как тип (SmallInt, то выше): 65 534, 65 533 и т. п. Сначала в этом списке идут несколько сотен resourcestring-констант, описанных в VCL (из модулей, чье имя заканчивается на const или consts: sysconst, DBConsts и т. п.). Затем очередь доходит до пользовательских констант (рис. 3.3).
С одной стороны, отсутствие лишних забот — это большой плюс; с другой стороны, разработчик не может задать строкам те номера, какие хочет.
Все остальное почти ничем не отличается от работы с "самодельными" ресурсами. Так выглядит перегружаемая версия конструктора нашего объекта EExceptionWithCode:
constructor EExceptionWithCode.CreateResCode(ResStringRec:
PResStringRec);
begin
FErrCode := ResStringRec^.Identifier;
inherited CreateRes(ResStringRec);
end;
А так — возбуждение самой ИС:
resourcestring sErrorl = 'Error1';
Raise EExceptionWithCode.CreateResCode
(PResStringRec(@sErrorl));
Результат обработки показан на рис. 3.3.
Рис. 3.3. Результат обработки ИС типа EExceptionWithCode
