4.4. Захист виділених ресурсів від зникнення
Коли програміст після компіляції отримує готовий до виконання файл, він щиро вірить, що програма буде працювати саме так, як він хоче. Поки вона в його дбайливих руках, так воно зазвичай і буває. Коли ж програма потрапляє в більш суворі умови - до нового користувачеві і на інший комп'ютер - з нею може статися все, що завгодно. "Новий господар" може замість очікуваних цифр ввести літери, витягти корінь з від'ємного числа, ділити на нуль і виконувати безліч інших необдуманих, часто випадкових дій. Особливо це стосується інтерактивних (діалогових) додатків, а таких - величезна більшість. З цього випливає, що програміст повинен організувати потужну оборону від будь-яких зазіхань на життєздатність своєї програми в процесі її виконання. Про те, як це зробити, розповідається в цій главі.
4.1. Помилки і виняткові ситуації
Ви повинні віддавати собі звіт в тому, що в будь-якому працюючому додатку можуть відбуватися помилки. Причини цих помилок бувають різними. Деякі з них носять суб'єктивний характер і викликані безграмотними діями програміста. Але існують і об'єктивні помилки, їх не можна уникнути при проектуванні програми, але можна виявити під час її роботи. Прикладів таких помилок скільки завгодно: недостатній обсяг вільної пам'яті, відсутність файлу на диску, вихід значень вихідних даних з допустимого діапазону і т.д.
Хороша програма повинна справлятися зі своїми помилками і працювати далі, не зациклюючись і не зависаючи ні за яких обставин. Для обробки помилок можна, звичайно, намагатися використовувати структури виду if
Виняткова ситуація (exception) - це переривання нормального ходу роботи програми через неможливість правильно виконати наступні дії.
Механізм обробки виняткових ситуацій найкраще підходить для взаємодії програми з бібліотекою підпрограм. Підпрограми бібліотеки виявляють помилки, але в більшості випадків не знають, як на них реагувати. Зухвала програма, навпаки, знає, що робити у разі виникнення помилок, але, як правило, не вміє їх своєчасно виявляти. Завдяки механізму обробки виняткових ситуацій забезпечується зв'язок між бібліотекою і використовує її програмою при обробці помилок.
Механізм обробки виняткових ситуацій досить складний у своїй реалізації, але для програміста він простий і прозорий. Для його використання в мову Delphi введені спеціальні конструкції try. except. end. try. finally. end і оператор raise. розглянуті в цьому розділі.
4.2. Класи виняткових ситуацій
Виняткові ситуації в мові Delphi описуються класами. Кожен клас відповідає певному типу виняткових ситуацій. Коли в програмі виникає виняткова ситуація, створюється об'єкт відповідного класу, який переносить інформацію про цю ситуацію з місця виникнення до місця обробки.
Класи виняткових ситуацій утворюють ієрархію, коренем якої є клас Exception. Клас Exception описує самий загальний тип виняткових ситуацій, а його спадкоємці - конкретні види таких ситуацій (таблиця 4.1). Наприклад, клас EOutOfMemory породжений від Exception і описує ситуацію, коли вільна оперативна пам'ять вичерпана.
У наступній таблиці наведено стандартні класи виняткових ситуацій, оголошені в модулі SysUtils. Вони покривають практично весь спектр можливих помилок. Якщо їх все-таки виявиться недостатньо, ви можете оголосити нові класи виняткових ситуацій, породжені від класу Exception або його спадкоємців.
Клас виняткових ситуацій
«Мовчазна» виняткова ситуація, яка використовується для виходу з декількох рівнів вкладених блоків або підпрограм. При цьому на екран не видається ніяких повідомлень про помилку. Для генерації виняткової ситуації класу EAbort потрібно викликати стандартну процедуру Abort.
Помилка доступу до файлу або пристрою введення-виведення. Код помилки міститься в поле ErrorCode.
Виняткова ситуація, що виникла поза програмою, наприклад, в операційній системі.
Виняткова ситуація, що виникла за межами програми, наприклад в DLL-бібліотеці, розробленої на мові C ++.
Загальний клас виняткових ситуацій, що виникають при роботі з динамічною пам'яттю. Є базовим для класів EOutOfMemory і EInvalidPointer.Вніманіе! Створення виняткових ситуацій цього класу (і всіх його нащадків) повністю бере на себе середовище Delphi, тому ніколи не створюйте такі виняткові ситуації за допомогою оператора raise.
Вільна оперативна пам'ять вичерпана (див. EHeadException).
Спроба звільнити недійсний покажчик (див. EHeadException). Зазвичай це означає, що покажчик вже звільнений.
Загальний клас виняткових ситуацій цілочисельний арифметики, від якого породжені класи EDivByZero, ERangeError і EIntOverflow.
Спроба ділення цілого числа на нуль.
Пошук відповідного обробника виконується послідовно до тих пір, поки клас виняткової ситуації не виявиться сумісним з класом, зазначеним в операторі on. Як тільки обробник знайдений, Випоняемие оператор, що стоїть за словом do і управління передається за секцію except. end. Якщо виняткова ситуація не відноситься ні до одного із зазначених класів, то управління передається в зовнішній блок try. except. end і обробник шукається в ньому.
Зверніть увагу, що порядок операторів on має значення, оскільки розпізнавання виняткових ситуацій повинно відбуватися від приватних класів до загальних класів, інакше кажучи, від нащадків до предків. З чим це пов'язано? Зараз зрозумієте. Уявіть, до чого призведе зміна порядку операторів on в прикладі вище, якщо взяти до уваги, що клас EMathError є базовим для EZeroDivide. Відповідь проста: обробник EMathError буде поглинати все помилки речової математики, в тому числі EZeroDivide. в результаті обробник EZeroDivide ніколи не виконається.
На найвищому рівні програми буває необхідно перехоплювати всі виняткові ситуації, щоб в разі якоїсь неврахованої помилки коректно завершити додаток. Для цього застосовується так званий обробник за умовчанням (default exception handler). Він записується в секції except після всіх операторів on і починається ключовим словом else:
4.3.3. Приклад обробки виключної ситуації
Як приклад обробки виняткової ситуації розглянемо дві функції: StringToCardinal і StringToCardinalDef.
Функція StringToCardinal виконує перетворення рядка в число з типом Cardinal. Якщо перетворення неможливо, функція створює виняткову ситуацію класу EConvertError.
Функція StringToCardinalDef також виконує перетворення рядка в число з типом Cardinal, але на відміну від функції StringToCardinal вона не створює виняткову ситуацію. Замість цього вона дозволяє задати значення, яке повертається в разі невдалої спроби перетворення:
Для перетворення вихідної рядки в число використовується певна вище функція StringToCardinal. Якщо при перетворенні виникає виняткова ситуація, то вона «поглинається» функцією StringToCardinalDef, яка в цьому випадку повертає значення параметра Default. Якщо відбувається якась інша помилка (НЕ EConvertError), то управління передається зовнішньому блоку обробки виняткових ситуацій, з якого була викликана функція StringToCardinalDef.
Приклад дуже простий, але добре демонструє переваги виняткових ситуацій перед традиційною обробкою помилок. Уявіть більш складні обчислення, що складаються з безлічі операторів, в кожному з яких може статися помилка. Наскільки складною виявиться обробка помилок численними операторами if і наскільки простий оператором try.
4.3.4. Поновлення виняткової ситуації
У тих випадках, коли захищений блок не може обробити виняткову ситуацію повністю, він виконує тільки свою частину роботи і відновлює виняткову ситуацію з тим, щоб її обробку продовжив зовнішній захищений блок:
Якщо жоден із зовнішніх захищених блоків не обробив виняткову ситуацію, то управління передається стандартному обробнику виняткової ситуації, що завершує програму.
4.3.5. Доступ до об'єкту, що описує виняткову ситуацію
При обробці виняткової ситуації може знадобитися доступ до об'єкта, що описує цю ситуацію і містить код помилки, текстовий опис помилки і т.д. У цьому випадку використовується розширена запис оператора on:
Мінлива E - це об'єкт виняткової ситуації, ShowMessage - процедура модуля DIALOGS, що відображає на екрані невелике вікно з текстом і кнопкою OK. Властивість Message типу string визначено в класі Exception. воно містить текстовий опис помилки. Початкове значення для тексту повідомлення вказується при конструюванні об'єкта виняткової ситуації.
Зверніть увагу, що після обробки виняткової ситуації звільнення відповідного об'єкта виконується автоматично, вам цього робити не треба.
4.4. Захист виділених ресурсів від зникнення
4.4.1. Витік ресурсів і захист від неї
Програми, побудовані з використанням механізму виняткових ситуацій, зобов'язані дотримуватися строгих правил розподілу і звільнення таких ресурсів, як пам'ять, файли, ресурси операційної системи.
Уявіть ситуацію: підпрограма розподіляє деякий ресурс, але виняткова ситуація перериває її виконання, і ресурс залишається не звільненим. Навіть подумати страшно, до чого може призвести така помилка: витік пам'яті, файлових дескрипторів, інших ресурсів операційної системи. Отже, ресурси потребують захисту від виняткових ситуацій. Для цього в середовищі Delphi передбачений ще один варіант захищеного блоку:
Особливість цього блоку полягає в тому, що секція finally. end виконується завжди незалежно від того, відбувається виняткова ситуація чи ні. Якщо який-небудь оператор секції try. finally генерує виняткову ситуацію, то спочатку виконується секція finally. end. звана секцією завершення (звільнення ресурсів), а потім управління передається зовнішньому захищеному блоку. Якщо все захищаються оператори виконуються без помилок, то секція завершення теж працює, але управління передається наступному за нею оператору. Зверніть увагу, що секція finally. end і не виконує жодних виняткову ситуацію, в ній немає ні коштів її виявлення, ні засобів доступу до об'єкта виняткової ситуації.
Малюнок 4.2. Логіка роботи оператора try ... except ... end
Блок try. finally. end володіє ще однією важливою особливістю. Якщо він поміщений в цикл, то виклик із захищеного блоку процедури Break з метою передчасного виходу з циклу або процедури Continue з метою переходу на наступну ітерацію циклу спочатку забезпечує виконання секції finally. end. а потім вже виконується відповідний перехід. Це твердження справедливо також і для процедури Exit (вихід з підпрограми).
Як показує практика, підпрограми часто розподіляють відразу кілька ресурсів і використовують їх разом. У таких випадках застосовуються вкладені блоки try. finally. end:
Крім того, ви успішно можете комбінувати блоки try. finally. end і try. except. end для захисту ресурсів та обробки виняткових ситуацій.
4.5. підсумки
У цьому розділі ви дізналися багато про виняткові ситуації і способи боротьби з ними. Тепер ваші програми напевно дадуть гідну відсіч не тільки самому необтесаного користувачеві, але і його дерев'яному комп'ютера. Це, до речі кажучи, одна з необхідних якостей, які дозволять віднести вашу програму до класу хороших. Дозволимо собі також нагадати, що тут були розглянуті тільки помилки часу виконання, тому не забудьте прочитати гл.10, де розказано про боротьбу з логічними помилками.