Програмування на мові Delphi. Глава 4. Виняткові ситуації і надійне програмування, Різне, Програмування, статті

попередня стаття серії


Зміст



Коли програміст після компіляції отримує готовий до виконання файл, він щиро вірить, що програма буде працювати саме так, як він хоче. Поки вона в його турботливих руках, так воно зазвичай і буває. Коли ж програма потрапляє в більш суворі умови – до нового користувача і на інший комп'ютер – з нею може статися все, що завгодно. "Новий господар" може замість очікуваних цифр ввести літери, витягти корінь з від'ємного числа, ділити на нуль і виконувати безліч інших необдуманих, часто випадкових дій. Особливо це стосується інтерактивних (діалогових) додатків, а таких – величезна більшість. З цього випливає, що програміст повинен організувати потужну оборону від усіх посягань на життєздатність своєї програми в процесі її виконання. Про те, як це зробити, розповідається в цій главі.

Помилки і виняткові ситуації


Ви повинні віддавати собі звіт в тому, що в будь-якому працюючому додатку можуть відбуватися помилки. Причини цих помилок бувають різними. Деякі з них носять суб'єктивний характер і викликані безграмотними діями програміста. Але існують і об'єктивні помилки, їх не можна уникнути при проектуванні програми, але можна виявити під час її роботи. Прикладів таких помилок скільки завгодно: недостатній обсяг вільної пам'яті, відсутність файлу на диску, вихід значень вихідних даних з допустимого діапазону і т.д.

Гарна програма повинна справлятися зі своїми помилками і працювати далі, не зациклюючись і не зависаючи ні за яких обставин. Для обробки помилок можна, звичайно, намагатися використовувати структури виду if <error> then Exit. Однак у цьому випадку ваш стрункий і красивий алгоритм вирішення основного завдання обросте потворними перевірками так, що через тиждень ви самі в ньому не розберетеся. З цієї майже тупикової ситуації середу Delphi пропонує простий і елегантний вихід – механізм обробки виняткових ситуацій.

Виняткова ситуація (Exception) – це переривання нормального ходу роботи програми через неможливість правильно виконати наступні дії.

Уявімо, що підпрограма виділяє область динамічної пам'яті і завантажує в неї вміст деякого файлу. Якщо в системі виявиться недостатньо пам'яті, то дані буде ніде розмістити і спроба завантажити файл призведе до помилки. Швидше за все, вся програма буде завершена аварійно через те, що оператор завантаження даних звернеться по недоступному для програми адресою. Як цього уникнути? При виявленні проблеми підпрограма повинна створити виняткову ситуацію – перервати нормальний хід своєї роботи і передати управління тим операторам, які зможуть обробити помилку. Як правило, оператори обробки виняткових ситуацій знаходяться в одній з викликають підпрограм.

Механізм обробки виняткових ситуацій найкраще підходить для взаємодії програми з бібліотекою підпрограм. Підпрограми бібліотеки виявляють помилки, але в більшості випадків не знають, як на них реагувати. Зухвала програма, навпаки, знає, що робити при виникненні помилок, але, як правило, не вміє їх своєчасно виявляти. Завдяки механізму обробки виняткових ситуацій забезпечується зв'язок між бібліотекою і використовує її програмою при обробці помилок.

Механізм обробки виняткових ситуацій досить складний у своїй реалізації, але для програміста він простий і прозорий. Для його використання в мову Delphi введені спеціальні конструкції try…except…end, try…finally…end і оператор raise, Розглянуті в цьому розділі.

Класи виняткових ситуацій


Виняткові ситуації в мові Delphi описуються класами. Кожен клас відповідає певному типу виняткових ситуацій. Коли в програмі виникає виняткова ситуація, створюється об'єкт відповідного класу, який переносить інформацію про цю ситуацію з місця виникнення до місця обробки.

Класи виняткових ситуацій утворюють ієрархію, коренем якої є клас Exception. Клас Exception описує найзагальніший тип виняткових ситуацій, а його спадкоємці – конкретні види таких ситуацій (таблиця 4.1). Наприклад, клас EOutOfMemory породжений від Exception і описує ситуацію, коли вільна оперативна пам'ять вичерпана.

У наступній таблиці наведено стандартні класи виняткових ситуацій, оголошені в модулі SysUtils. Вони покривають практично весь спектр можливих помилок. Якщо їх все-таки виявиться недостатньо, ви можете оголосити нові класи виняткових ситуацій, породжені від класу Exception або його спадкоємців.




















































































Клас виняткових ситуацій

Опис

EAbort "Мовчазна" виняткова ситуація, яка використовується для виходу з декількох рівнів вкладених блоків або підпрограм. При цьому на екран не видається ніяких повідомлень про помилку. Для генерації виключної ситуації класу EAbort потрібно викликати стандартну процедуру Abort.
EInOutError Помилка доступу до файлу або пристрою введення-виведення. Код помилки міститься в поле ErrorCode.
EExternal Виняткова ситуація, що виникла поза програмою, наприклад, в операційній системі.
EExternalException Виняткова ситуація, що виникла за межами програми, наприклад в DLL-бібліотеці, розробленої на мові C + +.
EHeapException Загальний клас виняткових ситуацій, що виникають при роботі з динамічною пам'яттю. Є базовим для класів EOutOfMemory і EInvalidPointer.Вніманіе! Створення виняткових ситуацій цього класу (і всіх його нащадків) повністю бере на себе середовище Delphi, тому ніколи не створюйте такі виняткові ситуації за допомогою оператора raise.
EOutOfMemory Вільна оперативна пам'ять вичерпана (див. EHeadException).
EInvalidPointer Спроба звільнити недійсний вказівник (див. EHeadException). Зазвичай це означає, що покажчик вже звільнений.
EIntError Загальний клас виняткових ситуацій цілочисельний арифметики, від якого породжені класи EDivByZero, ERangeError і EIntOverflow.
EDivByZero Спроба ділення цілого числа на нуль.
ERangeError Вихід за межі діапазону цілого числа або результату цілочисельного виразу.
EIntOverflow Переповнення в результаті целочисленной операції.
EMathError Загальний клас виняткових ситуацій речової математики, від якого породжені класи EInvalidOp, EZeroDivide, EOverflow і EUnderflow.
EInvalidOp Невірний код операції речової математики.
EZeroDivide Спроба ділення дійсного числа на нуль.
EOverflow Втрата старших розрядів дійсного числа в результаті переповнення розрядної сітки.
EUnderflow Втрата молодших розрядів дійсного числа в результаті переповнення розрядної сітки.
EInvalidCast Невдала спроба приведення об'єкта до іншого класу за допомогою оператора as.
EConvertError Помилка перетворення даних за допомогою функцій IntToStr, StrToInt, StrToFloat, StrToDateTime.
EVariantError Неможливість перетворення варьируемой змінної з одного формату в іншій.
EAccessViolation Додаток здійснило доступ до невірного адресою в пам'яті. Зазвичай це означає, що програма звернулася за даними по неініціалізованих вказівником.
EPrivilege Спроба виконати привілейовану інструкцію процесора, на яку програма не має права.
EStackOverflow Стек програми не може бути більше збільшений.
EControlC Під час роботи консольного застосування користувач натиснув комбінацію клавіш Ctrl + C.
EAssertionFailed Виникає при виклику процедури Assert, коли перший параметр дорівнює значенню False.
EPackageError Проблема під час завантаження та ініціалізації бібліотеки компонентів.
EOSError Виняткова ситуація, що виникла в операційній системі.

Таблиця 1. Класи виняткових ситуацій

Спадкування класів дозволяє створювати сімейства родинних виняткових ситуацій. Прикладом такого сімейства є класи виняткових ситуацій речової математики, які оголошені в модулі SysUtils наступним чином.

type
EMathError = class(Exception);
EInvalidOp = class(EMathError);
EZeroDivide = class(EMathError);
EOverflow = class(EMathError);
EUnderflow = class(EMathError);

Клас виняткових ситуацій EMathError є базовим для класів EInvalidOp, EZeroDivide, EOverflow і EUnderflow, тому, обробляючи виняткові ситуації класу EMathError, ви будете обробляти всі помилки речової математики, включаючи EInvalidOp, EZeroDivide, EOverflow і EUnderflow.

Неважко помітити, що імена класів винятків починаються з букви E (Від слова Exception). Цього правила корисно дотримуватися при оголошенні власних класів винятків, наприклад:

type
EMyException = class(Exception)
MyErrorCode: Integer;
end;

Як описуються класи виняткових ситуацій зрозуміло, розглянемо тепер, як такі ситуації обробляються.

Обробка виняткових ситуацій


Створення виняткової ситуації


Ідея обробки виняткових ситуацій полягає в наступному. Коли підпрограма стикається з неможливістю виконання подальших дій, вона створює об'єкт з описом помилки і перериває нормальний хід своєї роботи за допомогою оператора raise. Так виникає виняткова ситуація.

 raise EOutOfMemory.Create (Малувато пам'яті);

Даний оператор створює об'єкт класу EOutOfMemory (Клас помилок вичерпання пам'яті) і перериває нормальне виконання програми. Викликають підпрограми можуть цю виняткову ситуацію перехопити і обробити. Для цього в них організується так званий захищений блок:

try
/ / Захищаються від помилок оператори
except
/ / Оператори обробки виключної ситуації
end;

Між словами try і except поміщаються захищаються від помилок оператори. Якщо при виконанні будь-якого з цих операторів виникає виняткова ситуація, то управління передається операторам між словами except і end, Що створює блок обробки виняткових ситуацій. При нормальному (безпомилковому) виконанні програми блок exceptend пропускається (рисунок 1).

Малюнок 1. Логіка роботи оператора try … except … end

При написанні програми ви можете використовувати вкладені захищені блоки, щоб організувати локальну і глобальну обробку виняткових ситуацій. Концептуально це виглядає наступним чином:

try
/ / Захищаються оператори
try
/ / Захищаються оператори
except
/ / Локальна обробка виняткових ситуацій
end;
/ / Захищаються оператори
except
/ / Глобальна обробка виняткових ситуацій
end;

Виняткові ситуації зовнішнього захищеного блоку, що виникають за межами вкладеного блоку, обробляються зовнішньої секцією exceptend. Виняткові ситуації вкладеного захищеного блоку обробляються вкладеної секцією exceptend.

Розпізнавання класу виняткової ситуації


Розпізнавання класу виняткової ситуації виконується за допомогою конструкцій

 on <клас виняткової ситуації> do <оператор>;

які записуються в секції обробки виключної ситуації, наприклад:

try
/ / Обчислення з числами
except
on EZeroDivide do … ; / / Обробка помилки ділення на нуль
on EMathError do … ; / / Обробка інших помилок речової математики
end;

Пошук відповідного обробника виконується послідовно до тих пір, поки клас виняткової ситуації не виявиться сумісним з класом, зазначеним в операторі on. Як тільки обробник знайдено, випоняемие оператор, що стоїть за словом do і управління передається за секцію exceptend. Якщо виняткова ситуація не належить до жодного із зазначених класів, то управління передається в зовнішній блок tryexceptend і обробник шукається в ньому.

Зверніть увагу, що порядок операторів on має значення, оскільки розпізнавання виняткових ситуацій має походити від приватних класів до загальних класів, інакше кажучи, від нащадків до предків. З чим це пов'язано? Зараз зрозумієте. Уявіть, до чого призведе зміна порядку операторів on у прикладі вище, якщо взяти до уваги, що клас EMathError є базовим для EZeroDivide. Відповідь проста: обробник EMathError буде поглинати всі помилки речової математики, в тому числі EZeroDivide, В результаті обробник EZeroDivide ніколи не виконається.

На найвищому рівні програми буває необхідно перехоплювати всі виняткові ситуації, щоб у разі якоїсь необлікованої помилки коректно завершити програму. Для цього застосовується так званий обробник за умовчанням (Default exception handler). Він записується в секції except після всіх операторів on і починається ключовим словом else:

try
{Обчислення з числами}
except
on EZeroDivide do {обробка помилки ділення на нуль};
on EMathError do {обробка інших помилок речової математики};
else {обробка всіх інших помилок (обробник за умовчанням)};
end;

Врахуйте, що відсутність частини else відповідає запису else raise, Яке немає сенсу використовувати явно. Ми зі свого боку взагалі не радимо вам користуватися обробкою виняткових ситуацій за замовчуванням, оскільки всі ваші програми будуть будуватися, як правило, на основі бібліотеки VCL, в якій обробка за умовчанням вже передбачена.

Приклад обробки виключної ситуації


Як приклад обробки виключної ситуації розглянемо дві функції: StringToCardinal і StringToCardinalDef.

Функція StringToCardinal виконує перетворення рядка в число з типом Cardinal. Якщо перетворення неможливо, функція створює виняткову ситуацію класу EConvertError.

function StringToCardinal(const S: string): Cardinal;
var
I: Integer;
B: Cardinal;
begin
Result := 0;
B := 1;
for I := Length(S) downto 1 do
begin
if not (S[I] in [0..9]) then
raise EConvertError.Create (S + is not a valid cardinal value);
Result := Result + B * (Ord(S[I]) – Ord(0));
B := B * 10;
end;
end;

Функція StringToCardinalDef також виконує перетворення рядка в число з типом Cardinal, але на відміну від функції StringToCardinal вона не створює виняткову ситуацію. Замість цього вона дозволяє задати значення, яке повертається в разі невдалої спроби перетворення:

 function StringToCardinalDef (const S: string; Default: Cardinal = 0): Cardinal;
begin
try
Result := StringToCardinal(S);
except
on EConvertError do
Result := Default;
end;
end;


Для перетворення вихідного рядка в число використовується певна вище функція StringToCardinal. Якщо при перетворенні виникає виняткова ситуація, то вона "поглинається" функцією StringToCardinalDef, яка в цьому випадку повертає значення параметра Default. Якщо відбувається яка-небудь інша помилка (не EConvertError), то управління передається зовнішньому блоку обробки виняткових ситуацій, з якого була викликана функція StringToCardinalDef.

Приклад дуже простий, але добре демонструє переваги виняткових ситуацій перед традиційною обробкою помилок. Уявіть більш складні обчислення, що складаються з безлічі операторів, в кожному з яких може статися помилка. Наскільки складною виявиться обробка помилок численними операторами if і наскільки простий оператором try.

Поновлення виняткової ситуації


У тих випадках, коли захищений блок не може обробити виняткову ситуацію повністю, він виконує тільки свою частину роботи і відновлює виняткову ситуацію з тим, щоб її обробку продовжив зовнішній захищений блок:

try
/ / Обчислення з числами
except
on EZeroDivide do
begin
/ / Часткова обробка помилки
raise; / / відновлення виняткової ситуації
end;
end;

Якщо ні один із зовнішніх захищених блоків не обробив виняткову ситуацію, то управління передається стандартному обробникові виняткової ситуації, що завершує програму.

Доступ до об'єкту, що описує виняткову ситуацію


При обробці виняткової ситуації може знадобитися доступ до об'єкта, що описує цю ситуацію і містить код помилки, текстовий опис помилки і т.д. У цьому випадку використовується розширена запис оператора on:

 on <ідентифікатор об'єкта>: <клас виняткової ситуації> do <оператор>;

Наприклад, об'єкт виняткової ситуації потрібен для того, щоб видати користувачеві повідомлення про помилку:

try
/ / Захищаються оператори
except
on E: EOutOfMemory do
ShowMessage(E.Message);
end;

Мінлива E – Це об'єкт виняткової ситуації, ShowMessage – Процедура модуля DIALOGS, що відображає на екрані невелике вікно з текстом і кнопкою OK. Властивість Message типу string визначено в класі Exception, Воно містить текстовий опис помилки. Початкове значення для тексту повідомлення вказується при конструюванні об'єкта виняткової ситуації.

Зверніть увагу, що після обробки виключної ситуації звільнення відповідного об'єкта виконується автоматично, вам цього робити не треба.

Захист виділених ресурсів від зникнення


Витік ресурсів і захист від неї


Програми, побудовані з використанням механізму виняткових ситуацій, зобов'язані дотримуватися суворих правил розподілу та звільнення таких ресурсів, як пам'ять, файли, ресурси операційної системи.

Уявіть ситуацію: підпрограма розподіляє деякий ресурс, але виняткова ситуація перериває її виконання, і ресурс залишається не звільненим. Навіть подумати страшно, до чого може призвести така помилка: витік пам'яті, файлових дескрипторів, інших ресурсів операційної системи. Отже, ресурси потребують захисту від виняткових ситуацій. Для цього в середовищі Delphi передбачений ще один варіант захищеного блоку:

 / / Запит ресурсу
try
/ / Захищаються оператори, які використовують ресурс
finally
/ / Звільнення ресурсу
end;

Особливість цього блоку полягає в тому, що секція finallyend виконується завжди незалежно від того, відбувається виняткова ситуація чи ні. Якщо який-небудь оператор секції tryfinally генерує виняткову ситуацію, то спочатку виконується секція finally … end, звана секцією завершення (звільнення ресурсів), а потім управління передається зовнішньому захищеного блоку. Якщо все захищаються оператори виконуються без помилок, то секція завершення теж працює, але управління передається наступному за нею оператору. Зверніть увагу, що секція finallyend не обробляє виняткову ситуацію, в ній немає ні засобів її виявлення, ні засобів доступу до об'єкта виняткової ситуації.

Рисунок 2. Логіка роботи оператора try … except … end

Блок tryfinallyend володіє ще однією важливою особливістю. Якщо він поміщений в цикл, то виклик із захищеного блоку процедури Break з метою передчасного виходу з циклу або процедур Continue з метою переходу на наступну ітерацію циклу спочатку забезпечує виконання секції finallyend, А потім вже виконується відповідний перехід. Це твердження справедливо також і для процедури Exit (вихід з підпрограми).

Як показує практика, підпрограми часто розподіляють відразу кілька ресурсів і використовують їх разом. У таких випадках застосовуються вкладені блоки tryfinallyend:

/ / Розподіл першого ресурсу
try

/ / Розподіл другої ресурсу
try
/ / Використання обох ресурсів
finally
/ / Звільнення друга ресурсу
end;

finally
/ / Звільнення першого ресурсу
end;

Крім того, ви успішно можете комбінувати блоки tryfinallyend і tryexceptend для захисту ресурсів та обробки виняткових ситуацій.

Підсумки


У цьому розділі ви дізналися багато про виняткових ситуаціях і способи боротьби з ними. Тепер ваші програми напевно дадуть гідну відсіч не тільки самому необтесаного користувачеві, але і його дерев'яному комп'ютера. Це, до речі кажучи, одна з необхідних якостей, які дозволять віднести вашу програму до класу хороших. Дозволимо собі також нагадати, що тут були розглянуті тільки помилки часу виконання, тому не забудьте прочитати гл.10, де розказано про боротьбу з логічними помилками.

наступна стаття серії


Посилання по темі



Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*