Универсальный обработчик ошибок в delphi. Предотвращение и обработка ошибок

Меня учили обходиться без оператора Goto. Использование оператора безусловного перехода считалось моветоном равным по грешности полному отсутствию комментариев в программе.

А посему, считаю, что каждый вправе придумать свой алгоритм обработчика ошибок... Я лишь покажу один из возможных вариантов.

Первый вопрос, который я предвижу от слабо посвященных: "А зачем? "
Действительно, пишешь задачу, как "честная кнопка", пытаешься ничего не упустить, все предусмотреть, собственное творчество, как правило, себе нравится, и не вызывает сомнений правильность его алгоритма... И все же! Если задачка небольшая - все просто и прозрачно. Но, если алгоритм сложен... как говорят "глаз замыливается", перестает находить узкие места. К тому же накладывается человеческий фактор: откуда Вам было знать, что пользователь не введет значение в поле и нажмет "Enter"?.. В этот момент Ваша программа напугает его каким-нибудь англоязычным сообщением да еще и с пиктограммой "Error"... А, я с самого начала просил Вас уважать пользователя и заказчика. Стоит ли доводить ситуацию до общения с плохо вменяемым пиплом?! Если Вы не хотите общаться с человеком, у которого от испуга вытаращены глаза и трясутся руки, нужно научиться предугадывать (перехватывать) ошибки и выдавать на экран сообщения на родном для пользователя языке, желательно с номером Вашего телефона... Это и есть часть понятия "дружественный интерфейс".

Следующий вопрос, к которому я хочу приблизить тебя, дорогой читатель, это вопрос:

где и когда нужен обработчик ошибок?

Стоит ли, на эту тему рыться в учебниках? И "да", и "нет". Почему? Программа, о которой в настоящий момент идет речь ("Расходы"), работает с базой данных, а найти в книжках этот материал - ой как не просто, поскольку большую часть книжного пространства авторы отводят, как правило, обучению работе с базой данных (создание, подключение, запросы и т.п.). Можно долго и упорно рыться в интернете, гуглить яндекс и задавать вопросы на форумах - в конце концов, Вы соберете нужную инфу, но упустите время...

Я же со своей стороны предлагаю почаще спрашивать себя "А вдруг?"
А вдруг сервер не сохранит запись?
А вдруг пользователь не введет допустимое значение и что-нибудь нажмет, закроет окно и т.п.
А вдруг такая запись уже есть в базе данных?
А вдруг...

Чем большим количеством подобных вопросов Вы себя озадачите, тем меньше вероятность того, что Ваша программа в руках незадачливого пользователя перейдет в состояние "Не отвечает".

Наиболее общая схема обработчика ошибок выглядит примерно так:

  • Объявление необходимых переменных и присвоение им стартовых значений.
  • Выполнение основного алгоритма, в процессе которого проверяются возможные условия возникновения ошибок
  • Обработка ошибок
  • Выдача сообщения оператору

Я предлагаю вернуться к и на примере описанных в нем процедур "сочинить" несложный обработчик ошибок.

Добавьте в объявление переменных в процедуре SetLang() еще парочку:

ErrKod: integer;

ErrMsg: String;

а так же - объявление метки:

Label

ErrLabel;

и стартовые значения:


begin

ErrKod:=0; // Это значение при отсутствии ошибок

ErrMsg:= "Операция завершена успешно";

Настройте заранее (в начале процедуры) компонент, выдающий сообщения:

MyMessenger.TitleString:="Ошибка...";
MyMessenger.MessageType:=mtError;
MyMessenger.Buttons:=;

Затем - напишите проверку наличия файла языковой поддержки:

If Not FileExists(GetCurrentDir + "\"+Lang)
then
begin
ErrKod:=1;
ErrMsg:="При попытке открытия файла языковой настройки произошла ошибка";
end;

Почему именно здесь нужно предусмотреть какие-то действия? А вдруг в файле настроек указан несуществующий файл? Или его кто-то с диска того... нечаянно стер...

Следом должна идти проверка на наличие ошибок:

If ErrKod>0
then
Goto ErrLabel;


...

Если в результате этой проверки выясняется, что код ошибки стал больше нуля (а проверок может быть несколько и код ошибки может принимать десяток разных значений), то оператор безусловного перехода, минуя основную ветвь алгоритма, отправит нас на метку ErrLabel, после которой и написан нехитрый в данном случае обработчик ошибок:

...


ErrLabel:



MyMessenger.ShowMessage;


end; // Процедуры


Давайте нашалим: переименуем файл Rus.lng, например в Rus1.lng, чтоб программа его уж точно не нашла.

Тогда при старте Rashod.exe будет произведена попытка настроить интерфейс приложения с помощью указанного в файле настроек Rashod.ini, в секции , в переменной LangFileName файла Rus.lng, которого в рабочем каталоге нет. Сработает обработчик ошибки, и на экране будет выведено придуманное нами сообщение:


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

Верните переименованному файлу его законное название и запустите программу еще раз.
Вы получите еще одно сообщение:

не корректное по оформлению и неуместное по сути (если ошибок нет, нужно ли в данном случае вообще выводить какое-то сообщение?!).

Давайте поправим ситуацию:

ErrLabel:

Case ErrKod of

0: // Ошибок нет

Else

Begin

MyMessenger.MessageString:=ErrMsg;

MyMessenger.ShowMessage;

Разрабатывая какое-нибудь приложение, вы должны написать код, который будет решать поставленную задачу, а также код, который будет выполнять проверку на наличие ошибок. Как правило, код для обработки ошибок строится на основе оператора if . Оператор if часто используется для проверки данных, вводимых пользователем, а также результатов выполнения функций. В простых алгоритмах можно ограничиться применением оператора if , однако в приложениях с графическим интерфейсом пользователя, где пользователи имеют полную свободу действий, ошибки могут возникать когда угодно и где угодно. Использование одного только оператора if для защиты приложения - не самая лучшая идея.

С задачей перехвата ошибок и реагирования на них лучше всего справляется механизм обработки исключений (Структурированная обработка исключений (Structured exception handling – SHE ) представляет собой метод обработки ошибок, благодаря которому можно восстановить нормальную работу приложения после сбоя в работе программы, который в противном случае был бы фатальным) . Если в приложении, написанном с помощью Delphi, возникает ошибка, то приложение автоматически генерирует исключение. Исключением представляет собой объект, который описывает возникающую ошибку.

Генерация исключения означает всего лишь то, что приложение создало объект исключения и максимально подробно описало ошибку. Если мы не обрабатываем исключение (то есть не приготовлен специальный код для перехвата исключения), приложение само сделает это автоматически. Обычно приложение обрабатывает исключение, выводя на экран монитора окно с сообщением о возникшей ошибке. Например, если вы передадите функции StrToInt строку, содержащую пустую строку, то функция сгенерирует исключение.

Procedure TForm1.Button1Click(Sender: TObject); var x: Integer; begin x:= StrToInt(Edit1.Text); end;

Чтобы обработать исключение, сгенерированное функцией StrToInt , мы должны поместить вызов функции StrToInt в защищенный блок кода. Защищенным является блок кода, который может реагировать на некоторое исключение. В Delphi защищенный блок выглядит следующим образом:

try
оператор(ы)
except
операторы обработки исключения
end ;

Операторы, которые могут сгенерировать исключение, записываются в блоке try , а в обработчике исключений пишется код, который занимается обработкой исключений. Обработчик исключения является частью защищенного блока, начинающегося с зарезервированного слова except в Delphi.

Если вы передадите функции StrToInt допустимую строку, и при этом исключение не возникнет, будет выполнен только тот код, который находится в блоке try . Код в блоке исключения выполняется только в том случае, если оператор, находящийся внутри этого блока, сгенерирует исключение.

Procedure TForm1.Button1Click(Sender: TObject); var x: Integer; begin try x:= StrToInt(Edit1.Text); except ShowMessage("Ошибка преобразования"); end; end;

Обработка специфических исключений в Delphi

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

Чтобы разделить значения, введенные в компонентах TEdit , мы должны написать код, который сначала преобразует их в целые числа, а затем разделит одно на другое. Этот код может легко сгенерировать два исключения.

Одно из них, EConvertError , может быть сгенерировано в том случае, если значение одного из компонентов TEdit невозможно преобразовать к целому типу, а другое, EDivByZero , может быть сгенерировано тогда, когда предпринимается попытка разделить первое число на 0.

Procedure TForm1.Button1Click(Sender: TObject); var Num1,Num2: Integer; begin try Num1:= StrToInt(Edit1.Text); Num2:= StrToInt(Edit2.Text); ShowMessage("Результат: " + IntToStr(Num1 div Num2)); except ShowMessage("Вы не можете делить эти числа"); end; end;

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

on Некоторое-Исключение do Обработка_Исключения;

Конструкцию on-do можно использовать только в рамках обработчика исключений:

try
оператор (операторы);
except
on Исключение do Обработка_Исключения;
on Другое_Исключение do Его_Обработка;

По мере возможности, для обработки различных исключений лучше использовать конструкцию on-do . Например, вы можете обработать исключение EConvertError , выводя сообщение об ошибке, а исключение EDivByZero - уведомляя пользователя о том, что второе число не может быть равно нулю, и автоматически заменяя его единицей.

Procedure TForm1.Button1Click(Sender: TObject); var Num1,Num2: Integer; begin try Num1:= StrToInt(Edit1.Text); Num2:= StrToInt(Edit2.Text); ShowMessage("Результат: " + IntToStr(Num1 div Num2)); except on EConvertError do ShowMessage("Одно из чисел неправильно"); on EDivByZero do begin ShowMessage("Делитель не может быть равным 0"); Edit2.Text:= "1"; Edit2.SetFocus; end; // Завершение конструкции on EDivByZero do end; end;

Если конструкцию on-do использовать для обработки специфических исключений, вы должны также написать код для обработки ошибок, о которых вам ничего не будет известно. Чтобы обработать исключения, которые вам не удастся обработать специфическим образом, можно добавить к обработчику исключения часть else .

try
оператор (операторы);
except
on Исключение do Его_Обра6отка;
on Другое_Исключение do Его_Обработка;
else
Обработка_Всех_Остальных_Исключений;
end ;

Генерация исключений

Зарезервированное слово raise используется для генерации исключения. Чтобы сгенерировать исключение в Delphi, используйте зарезервированное слово raise , указывая вслед за ним экземпляр объекта исключения. Экземпляром объекта исключения обычно является вызов конструктора исключения.

Синтаксис генерации исключения обычно выглядит следующим образом:

raise Класс_Исключения.Create ("Сообщение_Об_Ошибке");

Вы можете, например, создать специальный вариант функции StrToInt , которая будет генерировать исключение EConvertError с помощью специальных сообщений об ошибке, если строку нельзя будет преобразовать в целое число.

Unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Edit1: TEdit; Edit2: TEdit; Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } function CustomStrToInt(const s:string): Integer; end; var Form1: TForm1; implementation {$R *.dfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject); var Num1, Num2: Integer; begin Num1:= CustomStrToInt(Edit1.Text); Num2:= CustomStrToInt(Edit2.Text); ShowMessage(IntToStr(Num1 div Num2)); end; function TForm1.CustomStrToInt(const s: string): Integer; var ErrorCode: Integer; begin Val(s, Result, ErrorCode); if ErrorCode 0 then begin if s = "" then raise EConvertError.Create("Пустая строка не может использоваться") else raise EConvertError.Create("Привет. Вы не можете конвертировать "" + s + "" в целое"); end; end; end.

Использование объекта исключения

Конструкция on-do позволяет получать на время объект исключения с помощью следующего синтаксиса

on Идентификатор: Исключение do Его_Обработка;

В качестве идентификатора обычно применяется заглавная буква Е . Когда вы получаете объект исключения, вы можете использовать его подобно любому другому объекту и даже обращаться к его свойствам и методам. Единственное, что не рекомендуется делать, это уничтожать объект исключения, поскольку объекты исключения автоматически управляются обработчиком исключения.

Procedure TForm1.Button1Click(Sender: TObject); var x,y: Integer; begin x:= 20; y:= 0; try Caption:= IntToStr(x div y); except on E: EDivByZero do ShowMessage("Exception: " + E.ClassType.ClassName + #13 + "Exception.Message: " + E.Message); end; end;

Создание специальных исключений в Delphi

Создать специальное исключение несложно, и этот процесс ничем не отличается от создания специального класса. Специальные исключения должны порождаться от класса Exception или другого потомка этого класса. Имена классов исключений должны начинаться с заглавной буквы Е .

type
EMyException = class(Exception);

В листинге ниже показана генерация и перехват специального исключения в Delphi.

Unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type ENoUpperCaseLetters = class(Exception); TForm1 = class(TForm) Label1: TLabel; Edit1: TEdit; Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } function CountUpperCase(const s: string): Integer; end; var Form1: TForm1; implementation {$R *.dfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject); var Cnt: Integer; begin try Cnt:= CountUpperCase(Edit1.Text); Caption:= IntToStr(Cnt) + " заглавных букв"; except on E: ENoUpperCaseLetters do Caption:= E.Message; end; end; function TForm1.CountUpperCase(const s: string): Integer; var ch: Char; begin Result:= 0; for ch in s do if ch in ["A".."Z"] then Inc(Result); {Вызываем исключение, если отсутствуют буквы в верхнем регистре} if Result = 0 then raise ENoUpperCaseLetters.Create("Нет заглваных букв"); end; end.

Защита распределения ресурсов

Зарезервированное слово try позволяет построить два различных блока: блок обработчика, исключений и блок защиты ресурсов. Блок обработчика исключений создается с помощью зарезервированного слова except , а блок защиты ресурсов- с помощью зарезервированного слова finally . Синтаксическая структура блока защиты ресурсов в Delphi выглядит следующим образом:

try
...
finally
...
end ;

Блоки обработки исключений и защиты ресурсов используются по-разному и работают также по-разному. Операторы обработчика исключений выполняются только в том случае, если операторы в блоке try сгенерировали исключение, а операторы в блоке finally выполняются всегда, даже если операторы в блоке try не сгенерировали никакого исключения. Если в блоке try возникнет исключение, управление будет передано блоку finally, после чего будет выполнен код очистки. Если в блоке try исключения не возникнут, операторы в блоке finally будут выполняться после операторов в блоке try .

Подходящим способом использования блока защиты ресурсов является распределение или, с другой стороны, затребование ресурса перед блоком try . После того как вы затребуете ресурс, поместите операторы, использующие ресурс, внутрь блока try . Когда работа с ресурсом будет завершена, вы должны будете освободить его. Операторы, освобождающие ресурс, должны быть написаны в блоке finally .

{ Запрос ресурса }
try
{ Использование полученного ресурса }
finally
{ Освобождение ресурса }
end ;

Блок защиты ресурса часто используется для того, чтобы обеспечить надлежащее освобождение динамически созданных объектов. Например, динамическое создание модальной формы необходимо всегда защищать с помощью блока try-finally .

Procedure TForm1.Button1Click(Sender: TObject); var NewForm: TForm; begin NewForm:= TForm.Create(Self); try NewForm.ShowModal; finally NewForm.Free; end; end;

В листинге ниже показан более короткий способ динамического создания формы, защищенной блоком try-finally .

Procedure TForm1.Button1Click(Sender: TObject); begin with TForm.Create(Self) do try ShowModal; finally Free; end; end;

Другое отличие между блоками обработки исключений и блоками обработки ресурсов заключается в том, что блок обработки ресурсов не обрабатывает исключения. Таким образом, если исключение возникнет, оно будет передано первому доступному обработчику исключений. Например, если вы выполните следующий код, то исключение EDivByZero приведет к тому, что обработчик исключений, используемый по умолчанию, выведет на экран монитора сообщение об ошибке, информирующее пользователя о возникшем исключении.

Procedure TForm1.Button1Click(Sender: TObject); begin with TForm.Create(Self) do try {Вызов исключения EDivByZero, поскольку значение Tag равно 0} Caption:= IntToStr(Top div Tag); ShowModal; finally Free; end; end;

Если вы хотите обработать исключение EDivByZero (или любое другое исключение) внутри блока защиты ресурсов, вы должны написать вложенный блок обработчика исключений.

Следующий листинг показывает, каким должен быть вложенный обработчик исключений внутри блока защиты ресурсов. Вы можете также вкладывать блоки защиты ресурсов в другие блоки защиты ресурсов или блоки обработки исключений.

Procedure TForm1.Button1Click(Sender: TObject); begin with TForm.Create(Self) do try try Caption:= IntToStr(Top div Tag); except on EDivByZero do Caption:= "Tag = 0"; end; ShowModal; finally Free; end; end;

ИЗМЕНЕНИЕ ОБРАБОТЧИКА ИСКЛЮЧЕНИЙ, ИСПОЛЬЗУЕМОГО ПО УМОЛЧАНИЮ

Глобальный объект Application отвечает за обработку исключений, не обрабатываемых блоком обработки исключений, который может находиться где-то в приложении. Чтобы изменить обработчик исключений, используемый по умолчанию, мы можем использовать компонент TApplicationEvents относящийся к категории Additional (Дополнительные).

Компонент TApplicationEvents предлагает событие OnException , которое генерируется всякий раз, когда возникает необработанное исключение.

Событие OnException может быть обработано с помощью процедуры типа TExceptionEvent . Процедура, обрабатывающая событие OnException , принимает два параметра: объект Sender и объект Exception .

procedure TMainForm.AppEventsException }