Комплексний контроль за якістю коду

Микола Мазуркін, www.mazurkin.virtualave.net

Зміст


  1. Введення
  2. Процедура Assert
  3. Номери рядків або унікальні мітки?
  4. Категорії помилок
  5. Тотальний контроль
  6. Висновок
  7. Приклад модуля з функціями комплексної обробки помилок
  8. Приклад використання системи комплексного контролю

1. Введення

Стаття описує систему методів і дій, які можуть бути використані при написанні великих проектів на Delphi. Основна мета системи полягає в ефективному підвищенні якості коду і можливості швидкого пошуку та виправлення виявлених помилок. В даний час, незважаючи на великі можливості системи Delphi і бібліотеки VCL, розробникам не запропонований простий і ефективний підхід до написання якісних програм.

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

Всі запропоновані тут методи не є єдино можливими і можуть бути змінені і покращені. Конкретний приклад використання даної системи наведено у доданому проекті.

2. Процедура ASSERT

У Object Pascal введена спеціалізована процедура Assert, призначення якої – Допомога в налагодженні коду і контролі над виконанням програми. Процедура є аналогом макросу ASSERT, який широко застосовується практично у всіх програмах, написаних з використанням C і C + + і їхніх бібліотек. Синтаксис процедури Assert (Макрос має схожий синтаксис) описаний нижче.



procedure Assert(Condition: Boolean; [Msg: string]);

Процедура перевіряє логічне твердження, що передається першим аргументом і, якщо це твердження хибне, то процедура виводить на екран діагностичне повідомлення з номером рядка і ім’ям модуля, де розташований виклик цієї процедури, і повідомлення користувача, яке опціонально передає другій аргументом. У деяких системах розробки, наприклад (MSVC + MFC), макрос Assert примусово завершує виконання програми після видачі відповідного діагностичного повідомлення. В інших системах (наприклад Delphi) стандартна процедура обмежується лише видачею діагностичного повідомлення.

Дія стандартної процедури Assert (як і відповідного макросу) залежить від режиму компіляції проекту. Зазвичай, в режимі налагодження процедура діє як описано вище, в інших же режимах, виклик даної процедури і перевірка умови ігноруються і не компілюються в проект. Вважається, що виключення перевірки в готової програмі дозволяє підвищити продуктивність і зменшити розмір програми.

У мовах С і С + + налагоджувальний режим компіляції задається визначенням (# define) відповідної константи, зазвичай це _DEBUG. У Object Pascal налагоджувальний режим включається спеціальною опцією компілятора. Крім того, в С і С + + макрос ASSERT – це звичайний макрос, нічим не відрізняється від безлічі інших макросів. Макрос використовує змінну компілятора __ LINE__, що дозволяє йому визначити номер рядка, в якої відбулося порушення перевірки. У Object Pascal такої змінної немає, і за реалізацію процедури Assert повністю відповідає компілятор, що дозволяє говорити про процедурі Assert, як про особливості компілятора і мови Object Pascal, а не як про звичайною процедурою в складі бібліотеки VCL.

Процедура Assert зазвичай застосовується в наступних випадках:

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



procedure AddElement(Elem: TObject; Index: Integer);
begin/ / Посилання на об’єкт не повинна бути порожньою Assert (Elem <> nil, ‘Порожня посилання.’);/ / Індекс повинен бути в межах норми
Assert((0 <= Index) and (Index < FCount), 'Невірний індекс ');   / / Щось робимо   . . .   / / Перевіряємо результат алгоритму   Assert (Result = 0, 'Помилка в алгоритмі вставки елемента '); end;

Перша процедура перевіряє, чи є передана посилання на об’єкт непорожній. Друга процедура перевіряє індекс додається елементу на приналежність до обмеженому діапазону. Третя процедура перевіряє правильність виконання алгоритму. Ясно, що при невиконанні хоча б однієї з цих умов, необхідновважати, що дана процедура виконалася неправильно, і подальше правильне виконання всієї програми не представляється можливим. Ясно також, що, так як дану процедуру (процедуру AddElement) викликаємо тільки ми (наша програма), така подія ніколи не повинно відбуватися, в припущенні, що алгоритм правильний і аргументи, що передаються в функцію, вірні. Однак, як відомо: “Людина вважає, а Бог розташовує”, і при неуважному програмуванні в великому проекті такий тип помилок ставати основним.

Без застосування цих перевірок, програма б видала малоінформативні повідомлення, наприклад про виключення певного типу, і звичайно без зазначення місця, де це виключення відбулося. Тому, у великому проекті, інтенсивне застосування процедури Assert дозволяє швидко локалізувати, ідентифікувати і усунути виниклу помилку в роботі алгоритму.

В даний час (в п’ятій версії Delphi і кілька більш ранніх версіях), процедура Assert генерує на місці свого виклику виключення типу EAssertionFailed і подальше проходження цього виключення здійснюється звичайним чином – вгору по стеку процедур до найближчого обробника виключень. Винятки від процедури Assert обробляються таким же чином, як і всі інші – об’єкт TApplication ловить все винятку і показує їх тип і їх повідомлення на екрані. Однак такий підхід важко вважати логічним. Винятки від процедури Assert докорінно відрізняються від інших винятків. Винятки від процедури Assert свідчать про серйозні помилки в логіці роботи програми і вимагають особливого уваги, аж до примусового завершення програми з виведенням особливого діагностичного повідомлення.

Реалізація процедури Assert в Object Pascal повністю покладена на компілятор (Вірніше на “наближений до двору” модуль system.pas), що дещо ускладнює зміна логіки роботи. Можливі три основні варіанти внесення змін до логіку роботи.



  1. Установка обробника події TApplication.OnException і обробка в ньому виключення з типом EAssertionFailed. Цей спосіб є найбільш простим, і менш гнучким. Підходить, якщо все що необхідно – це вивести на екран особливу повідомлення і примусово завершити програму.
  2. Пряма установка обробника процедури Assert, шляхом присвоєння адреси своєї процедури системної змінної AssertErrorProc. Область застосування практично та ж сама, що і в попередньому випадку. Однак, в цьому випадку, можливі і складніші маніпуляції. Наприклад, можна написати обробник, який не генерує виняток, а відразу виводить повідомлення і примусово завершує програму. Приклад такого обробника наведено нижче.



    procedure AssertHandlerMSG(const Msg, Module: string; Line: Integer;
    ErrorAddr: Pointer);
    begin/ / Виводимо повідомлення
    MessageBox(0,
    PChar(Format(‘Error “%s” at address 0x%8.8x in file %s, line %d.’,
    [Msg, Integer(ErrorAddr), Module, Line])),
    ‘Error’,
    MB_OK
    );/ / Виходимо з програми
    ExitProcess(1);
    end;
    . . ./ / Налаштування посилання на глобальний обробник процедури Assert
    AssertErrorProc := @AssertHandlerMSG;


  3. Написання процедури Assert заново. Самий гнучкий і зручний варіант. Наприклад, якщо вас не влаштовує стандартний синтаксис процедури Assert, і ви хочете передати в процедуру додаткові параметри (наприклад, тип помилки, тип генерованого виключення, код, і т.п.). При цьому виникає два особливих моменту пов’язаних з тим фактом, що за процедуру Assert відповідає компілятор. По-перше, ви не зможете управляти включенням і включенням викликів процедури Assert в програмі через стандартні опції. Процедура Assert буде виконуватися завжди. Єдине, що ви можете зробити – це відразу вийти з процедури, якщо режим роботи програми не налагоджувальний. По-друге, в самій процедурі неможливо дізнатися номер рядка, в якої стався виклик процедури. Тим не менш, обидві цих труднощі переборні.
Необхідність включення і виключення викликів процедури Assert, пов’язаних з режимом роботи програми є само собою непрямо. Вважається, що вимикання викликів Assert в налагодженій програмі дозволяє зменшити розмір програми і збільшити швидкість її роботи. Однак, економія на розмірі та збільшення швидкодії є досить малими (якщо ви не включаєте процедуру Assert в тіло кожного циклу). Що відбувається на цьому тлі, мовчазне з’їдання помилок в програмі ставить під сумнів необхідність відключати виклики процедури Assert в “готової” програмі. Те, що ви не знайшли помилок при розробці і налагодженні програми зовсім не означає, що там їх немає. Помилки можуть з’явитися у користувача програми, наприклад, в умовах, в яких програма не тестувалася.Як ви про них дізнаєтеся, якщо відключите виклики Assert? Звичайно, лукаве відключення попереджень може на час зберегти вашу репутацію, і користувач може і не дізнатися про ті жахи, які відбуваються в надрах вашої програми. А раптовий безіменний збій вашої програми ви можете списати на особливості роботи Windows. Однак якості вашій програмі це не додасть. Чесна реєстрація всіх помилок набагато поліпшить ваші програми.

Таким чином, перша трудність вирішена – її просто не потрібно вирішувати. Працюйте чесно, завжди залишайте виклики функцій Assert в програмі, і вони допоможуть діагностувати помилки. Рішення другої проблеми – зазначення номерів рядків у повідомленнях описано в наступному розділі. Приклад же найпростішої альтернативної процедури Assert наведено нижче. Функція називається AssertMsg і приймає ті ж параметри, що і стандартна процедура. Звичайно, список цих параметрів можна розширити, так само, як і змінити логіку роботи. Дана жпроцедура генерує виняток за адресою виклику цієї процедури, а глобальний оброблювач виключень виводить повідомлення і завершує програму при отриманні виключення з певному типом EFatalExcept.



procedure AssertMsg(Condition: Boolean; const Mark: string);
var
ExceptAddr : Pointer;
Begin/ / Якщо умова виконується, то виходимо
if Condition then Exit;/ / Отримуємо адресу викликала нас команди Call
asm
mov EAX, [EBP+$04]
mov ExceptAddr, EAX
end;/ / Збуджується виключення в місці виклику цієї функціїraise EFatalExcept.Create (‘Несподівана помилка.’ + # 13 # 13 + Mark) at ExceptAddr;
end;

3. Номери рядків або унікальні мітки?

У більшості систем розробки, в повідомленні, виведеному при активізації процедури Assert, виводиться три параметри: повідомлення користувача, ім’я вихідного модуля і номер рядка, в якому знаходився виклик процедури. При цьому основний упор при пошуку помилки робиться на номер рядка та ім’я модуля, а повідомлення користувача є малоінформативним. Однак, якщо ви вирішили залишити виклики процедури Assert в готовій поширюваної програмі, тут таїться проблема. При розробці програми ви будете випускати десятки і десятки версій, і природно, що вихідний код буде сильно модифікуватися, а номери ліній, на яких розташовуються перевірочні виклики, будуть постійно змінюватися. І якщо раптом ваш користувач повідомляє про те, що у вашій програмі спрацювала перевірка на лінії N в вихідному модулі M, ви повинні задатися питанням, яка версія програми знаходиться у користувача? Навіть якщо ви збережете всі вихідні тексти абсолютно всіх версій, такий підхід не можна назвати зручним і ясним, він таїть у собі приховану плутанину і безлад, оскільки вам доведеться відстежувати номери рядків в різних версіях програми при перегляді розвитку програми.

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

Що вибрати в якості унікальних ідентифікаторів? Можна вибрати будь-яке не повторюване число або рядок. Наприклад, послідовно передавати числа від нуля і далі з інкрементом на одиницю. Або передавати унікальні рядки, які можна придумувати на ходу. Однак, це обтяжлива заняття – пам’ятати останній набраний номер або рядок і не повторюватися. Можна написати спеціальний модуль для редактора в середовищі Delphi, який буде автоматично вводити ідентифікатори. Однак, якщо ви будете працювати над проектом на різних машинах, то виникне проблема синхронізації лічильників.

Існує прекрасний варіант вирішення цієї проблеми. У середовищі Delphi натисніть клавіші + + І ви отримаєте рядок схожу на цю: [‘{19619100-22B0-11D4-ACD0-009027350D25}’]. Це унікальний ідентифікатор GUID (Global Unique IDentificator), шестнадцатібайтное унікальне значення, перекладене у послідовну форму з розмежувачами і укладену в квадратні дужки. Таке значення унікальне, воно залежить від часу і від номера мережевої карти. Якщо мережевої карти немає, то номер генерується програмно. По крайней мірою, Microsoft стверджує, що такий номер ніколи не повториться. Кожен раз, коли винатиснете відповідну комбінацію клавіш, система згенерує новий унікальний код.Якщо видалити квадратні дужки з боків, то вийти відмінний унікальний строковий ідентифікатор, готовий до використання, при цьому виклик процедури AssertMsg буде виглядати подібним чином.



AssertMsg(FCount > 0, ‘{19619100-22B0-11D4-ACD0-009027350D25}’);

В даному випадку повідомлення користувача не передається – передається тільки унікальна мітка. У самому справі, яка різниця користувачеві, чому саме загинула ваша програма? Користувач лише повинен повідомити, що ваша програма працює з помилкою і передати інформацію, яка допоможе цю помилку знайти. Звичайно, подібний унікальний номер займає деяке місце в сегменті констант, але, як показує практика, не більше 5% від усього обсягу програми, і на додаток, такий номер дуже зручно записувати з екрану. Крім того, він дійсно ніколи не повторюється! Якщо ж ви – економ, і вам шкода витрачати 38 байт на кожен ідентифікатор, можете передавати в процедуру AssertMsg четирехбайтное число, яке можна отримати, взявши перші вісім шістнадцятирічних цифр з строкового ідентифікатора – саме вони змінюються найчастіше.



AssertMsg(FCount > 0, $19619100);

4. Категорії помилок

Останнім часом, практично у всіх розвинених об’єктно-орієнтованих мовах, з’явилася структурна обробка винятків. Ця можливість підтримується операційною системою і дозволяє перехоплювати нештатні ситуації виникають, як в звичайному, так і в захищеному режимі системи. Мова Object Pascal повністю підтримує всі можливості по обробці виключень. Використання виключень при розробці програм рекомендується документацією та підтримується широким використанням виключень в VCL. Типовий приклад обробки виключень наведено нижче.



try
FS := TMemoryStream.Create;
try
FS.LoadFromFile(‘sound.dat’);
. . .
finally
FS.Free;
end;
except
FSoundData := nil;
raise;
end;

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

За замовчуванням усі винятки обробляються однаково. Винятки, якщо вони не перехоплені обробником користувача, передаються в глобальний обробник винятків TApplication.HandleException. Оброблювач перевіряє тип винятку, а потім виводить діагностичне повідомлення, після чого виконання програми продовжується.

Неважливо, яке виключення виникло: нестача пам’яті, невдале виконання API функції, звернення до неіснуючого ділянки пам’яті, невдале відкриття файлу або бази даних, всі ці помилки породжують винятки, які обробляються однаковим чином! Між тим, всі ці помилки можна і потрібно розділити на класи, кожен з яких вимагає особливої ​​уваги і окремої обробки.

Перш за все, помилки потрібно розділити на дві групи – фатальні помилки і нефатального або відновлюваністю помилки. Фатальні помилки – це помилки “Несумісні з життям”, після яких виконання програми неможливо й безглуздо. Фатальна помилка завжди є несподіваною і мається на увазі малоймовірною. Наприклад, будь спрацьовування перевірки Assert означає серйозне порушення логіки роботи програми. Збій при виділенні пам’яті або ресурсів в більшості випадків також є невідновні. Переважна більшість API функцій при передачі в них правильних параметрів можна віднести до “надійним” функцій, тобто таким функціям, у виконанні яких ми впевнені, і невиконання яких малоймовірне, але призводить до фатальних помилок. Фатальні помилки можна віднести до серйозних недоліків або у програмі, або в операційній системі.

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

Грань між фатальними і восстановимой помилками дуже тонка і невизначена. В одному випадку помилку можна віднести до фатальних помилок, а в іншому – до відновити події. Але, тим не менше, кожна нештатна ситуація повинна бути чітко віднесенадо одного з цих двох класів.

У межі такого підходу, всі функції повинні бути оголошені ненадійними, і повинно бути предсмотрено відновлення нормальної роботи програми після будь нештатної ситуації.Однак, таке навряд чи можливо, оскільки в такому випадку 99% від обсягу програми будуть займатиперевірки і відновлення після збою при виконання будь-якої з функції. Таким чином, для успішноїі ефективної роботи, необхідно якомога більше функцій віднести до розряду надійних, і як можнаменше – до розряду ненадійних, що вимагають спеціальної обробки.

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

Таким чином, ми прийшли до розуміння того, що існують різні групи помилок, з різним алгоритмом їх обробки. Як уже згадувалося, всі помилки в бібліотеці VCL обробляються однаковим чином. Змінити існуючий стан речей можна різними способами. Наприклад, в можна аналізувати всі типи виключень в обробнику TApplication.OnException. Виключення таких типів як, наприклад,EAccessViolation, EListError, EAbstractError, EArrayError, EAssertionFailed і багатьох інших можна розглядати як фатальні помилки, а виключення решти типів розглядати як відновлюваністю помилки. При цьому відкат при відновлених помилках виконувати шляхом локального перехоплення виключення конструкцією try-except-end, обробки і подальшої генерації виключення інструкцією raise. Однак такий спосіб не є гнучким, оскільки один і той же тип виключення в однієї операції може розглядатися як восстановимой, а в іншій – як фатальний.

Поліпшеною різновидом такого способу є спосіб, коли всі відновлюваністю виключення від VCL перехоплюються і обробляються на місцях, а фатальні виключення транспортуються в глобальний обробник TApplication.OnException. Таким чином, глобальний обробник розглядає будь виняток як фатальне. Перехоплення відновлених винятків не займає багато місця, так як і було зазначено раніше, переважна більшість операцій можна і потрібно розглядати як “надійні” операції, тобто призводять до фатальних помилкам.

Наприклад, відкриття файлу можна проводити наступним чином.



try/ / Потенційно небезпечна операція
FS := TFileStream(‘sound.dat’, fmOpenRead);
exceptShowMessage (‘Неможливо відкрити файл даних’);
Exit;
end;/ / Перевірка розміру
if FS.Size <> 1000 then Exit;/ / “Надійна” операція – все повинно виконуватися нормально
FS.Position := 0;
FS.ReadBuffer(Fbuffer, 1000);

Виклики API функцій не генерують винятків, тому слід аналізувати і обробляти результати виконання потенційно небезпечних функцій, а “надійні” функції обгортати процедурою Win32Check, а ще краще спеціальною процедурою Assert із зазначенням коду помилки і унікальною мітки.



procedure AssertWin32(Condition: Boolean; Mark: string);
var
ExceptAddr : Pointer;
ExceptMsg : string;
begin/ / Якщо умова виконується, то виходимо
if Condition then Exit;/ / Отримуємо адресу викликала нас інструкції Call
asm
mov EAX, [EBP+$04]
mov ExceptAddr, EAX
end;/ / Отримуємо повідомлення про помилку
ExceptMsg := SysErrorMessage(GetLastError);/ / Збуджується виключення в місці виклику цієї функціїraise EFatalExcept.Create (‘Помилка Win32:’ + ExceptMsg + # 13 # 13 + Mark) at ExceptAddr;
end;

Таким чином, ключем до успіху є правильне розділення всіх викликів функцій у програмі на дві групи. Перша група – потенційно небезпечні, “ненадійні” функції повинні бути оброблені з особливою ретельністю і можливістю відновлення програми після збою цих функцій. Помилки при їх виконанні цілком можливі і повинні розглядатися як природні. Друга група – “надійні” функції, невиконання яких повинно приводити до висновку діагностичного повідомлення і закриття програми. Помилки при виконанні надійних функцій повинні розглядатися як фатальні.

Ви або повністю контролюєте ситуацію, з можливістю відкоту до нормального Станом програми – “нефатального помилка”; або вибачаєтеся і закриваєте програму,так як ви не можете коректно обробити цю помилку – “фатальна” помилка. У другому випадкуви повинні зробити все можливе, щоб інформація про помилку була якомога змістовніше, для того, щоб ви могли її виправити.

5. Тотальний контроль

У першій і другій главі обговорювалася важливість застосування в програмах процедури Assert з унікальними мітками для швидкої локалізації та ідентифікації помилок. В попередньому розділі обговорювалося принцип поділу всіх нештатних ситуацій на дві групи,вимагають різного підходу. Слідування цим принципам допоможе вам поліпшити якість коду при написанні великих проектів.

Однак існує ще одна проблема, пов’язана з тим, що широко використовувана бібліотека VCL, а також безліч інших природно не використовують вище зазначені принципи. Наприклад, при виконанні наступного коду виникне виключення від VCL з повідомленням “List index out of bounds “, так як ми запитуємо на один елемент більше, ніж є в списку. Отримавши повідомлення про таку помилку від користувача, ви навряд чи зможете визначити місце програми, де ця помилка сталася. Пошук такої помилки утруднений, навіть якщо вона виникла на вашому комп’ютері, на те, на якому ви розробляєте програму. А якщо така помилка виникла у далекого користувача, складність зростає у багато разів,так як вам належить ще і “вибити” всю інформацію про помилку в людини далекого від Delphi зокрема, і від програмування взагалі.



for i:=0 to FList.Count do
FObj := TObject(FList[i]);

Та ж сама ситуація станеться, якщо ви спробуєте звернутися по “скаженому” покажчику – покажчику в якому зберігається стороннє значення вказує в нікуди. Ви отримаєте малоінформативні повідомлення, яке ніяк не вказує на місце, де ця помилка сталася. Використовуючи вищеописані принципи, ви зможете перехопити цю помилку, розпізнати її як фатальну і закрити програму. Однак ви не зможете видати інформативне повідомлення про місце де ця помилка сталася.І її локалізація і виправлення будуть сильно утруднені.

Одним з успішних варіантів вирішення такої проблеми є приміщення кожній (Кожній!)процедури в конструкцію try-except-end. При цьому локальний обробник виключення ловить виниклу помилку і замінює її своєю із зазначенням унікального ідентифікатора. При цьому попередній приклад може виглядати так.



try
for i:=0 to FList.Count do FObj := TObject(FList[i]);
except
raise AssertInternal(‘{88A4613E-0ACB-11D4-ACCF-009027350D25}’);
end;

Процедура AssertInternal “успадковує” повідомлення від виниклого виключення, плюс, додає до нього свій унікальний ідентифікатор. При виникненні помилки, користувачеві буде видане відповідне попередження, після чого програма закриється. Можливий запис попереджуючого повідомлення в спеціальний файл, який згодом може бути висланий вам користувачем.

Отримавши інформацію про виниклу помилку ви можете:

Така інформація значно полегшує пошук і виправлення помилок в дуже великому проекті.

6. Висновок

Все вищезазначене було лише приблизними рецептом і їжею для роздуми. Точний варіант комплексної системи обробки помилок наведено в доданому архіві. Модуль ATSAssert.pas містить приклади процедур та функцій для обробки фатальних помилок. Модуль ATSDialogMain.pas містить приклади їх використання.

7. Приклад модуля з функціями комплексної обробки помилок


///////////////////////////////////////////////////////////////////////////////// / ATSTest – ілюстрація комплексної обробки виключень/ / (C) Микола Мазуркін
// _____________________________________________________________________________/ / Процедури обробки фатальних помилок
////////////////////////////////////////////////////////////////////////////////
unit ATSAssert;
interface
uses Windows, Messages, SysUtils, ActiveX, Forms;


Оголошення декларацій використовуваних функцій

Оголошується перелік та формат функцій для комлексной контролю за якістю коду.



///////////////////////////////////////////////////////////////////////////////// / Процедури обробки фатальних ситуацій
////////////////////////////////////////////////////////////////////////////////
procedure AssertMsg(Condition: Boolean; const Mark: string);
procedure AssertWin32(Condition: Boolean; const Mark: string);
function AssertInternal(const Mark: string): TObject;
procedure HandleError(const Msg: string);
implementation
////////////////////////////////////////////////////////////////////////////////
// Utils
// _____________________________________________________________________________/ / Процедури обробки фатальних ситуацій
////////////////////////////////////////////////////////////////////////////////


Глобальний обробник виключень

Глобальний обробник виключень встановлює процедуру обробки на подію Application.OnException. Це дозволяє перехоплювати всі виключення не піймані конструкцією try-except-AssertInternal-end. Хоча таких неспійманих винятків бути не повинно,краще все-таки поставити на них обробник.

Якщо ви пишіть бібліотеку DLL в такому обробнику немає необхідності, так як всі неспійманих винятку будуть обробляти в EXE-модулі.



////////////////////////////////////////////////////////////////////////////////
// TExceptHandler
// _____________________________________________________________________________/ / Глобальний обробник виключень
////////////////////////////////////////////////////////////////////////////////
type
TExceptHandler = class (TObject)
procedure OnException(Sender: TObject; E: Exception);
end;
var
ExceptHandler : TExceptHandler;
///////////////////////////////////////////////////////////////////////////////// / Обробка виключень
////////////////////////////////////////////////////////////////////////////////
procedure TExceptHandler.OnException(Sender: TObject; E: Exception);
begin
HandleError(PChar(E.Message));
end;


Основна процедура обробки помилок

Основна процедура обробки помилок. Формує і виводить повідомлення, записує його впосмертний лог і закриває програму. Якщо ви пишете бібліотеку DLL, то для кожної бібліотекиможете додати до повідомлення свій додатковий префікс, наприклад ‘A’, ‘B’, ‘C’ і т.д. Це допоможе швидко визначити в якому модулі сталася помилка.



///////////////////////////////////////////////////////////////////////////////// / Обробка помилкової ситуації
////////////////////////////////////////////////////////////////////////////////
procedure HandleError(const Msg: string);
var
DeadFile : TextFile;
DeadName : string;
ErrorMsg : string;
begin/ / Формуємо повідомлення
ErrorMsg := Msg + #13#13;/ / Якщо це виняток, то додамо його тип і адресу
if ExceptObject <> nil thenErrorMsg: = ErrorMsg + Format (‘Виключення типу% s, адреса 0x% 8.8x’ # 13 # 13,
[ExceptObject.ClassName, DWord(ExceptAddr)]
);ErrorMsg: = ErrorMsg + ‘Виконання програми буде припинено.’;/ / Показуємо діалог
MessageBox(0, PChar(ErrorMsg),’Фатальна помилка’, MB_OK + MB_ICONWARNING + MB_APPLMODAL + MB_SETFOREGROUND + MB_TOPMOST);/ / Генеруємо унікальне ім’я файлу
SetLength(DeadName, MAX_PATH+10);
GetTempFileName(PChar(ExtractFilePath(ParamStr(0))), ‘err’, 0, @DeadName[1]);/ / Записуємо помилку в посмертний лог – на випадок якщо користувач не здогадається/ / Записати код помилки і повідомлення.
AssignFile(DeadFile, DeadName);
ReWrite(DeadFile);
WriteLn(DeadFile, ErrorMsg);
CloseFile(DeadFile); / / Негайно закриваємо процес
ExitProcess(1);
end;


Альтернативна процедура Assert



///////////////////////////////////////////////////////////////////////////////// / Перевірка здійсненності умови
////////////////////////////////////////////////////////////////////////////////
procedure AssertMsg(Condition: Boolean; const Mark: string);
begin/ / Якщо умова виконується, то виходимо відразу
if Condition then Exit;/ / Обробляємо виниклу помилкуHandleError (‘Фатальна помилка’ + Mark);
end;


Альтернативна процедура Assert для API-функцій



///////////////////////////////////////////////////////////////////////////////// / Перевірка здійсненності API-функції
////////////////////////////////////////////////////////////////////////////////
procedure AssertWin32(Condition: Boolean; const Mark: string);
var
ErrorMsg : string;
begin/ / Якщо умова виконується, то виходимо
if Condition then Exit;/ / Отримуємо повідомлення про помилку
ErrorMsg := SysErrorMessage(GetLastError);/ / Обробляємо виниклу помилкуHandleError (‘Фатальна помилка’ + Mark + # 13 # 13 + ErrorMsg);
end;

Функція для тотального контролю винятків

Функція для перехоплення виключень від VCL і невдалих звернень до пам’яті. Добаляет до тексту виключенняунікальну мітку і генерує помилку.



///////////////////////////////////////////////////////////////////////////////// / Генерація фатального винятку
////////////////////////////////////////////////////////////////////////////////
function AssertInternal(const Mark: string): TObject;
begin/ / Перевіряємо тип згенерованого виключення і обробляємо виниклу помилку
if ExceptObject is Exception thenHandleError (‘Фатальна помилка’ + Mark + # 13 # 13 + PChar (Exception (ExceptObject). Message))
elseHandleError (‘Фатальна помилка’ + Mark);/ / Наступна строчка ніколи не виконається, вона потрібна для заспокоєння компілятора
Result := nil;
end;


Ініціалізація модуля

Створення та налаштування глобального обробника виключення на подію
TApplication.OnException.



///////////////////////////////////////////////////////////////////////////////// / Автоматичне створення глобального обробника виключення
////////////////////////////////////////////////////////////////////////////////
initialization
ExceptHandler := TExceptHandler.Create;
Application.OnException := ExceptHandler.OnException;
end.

8. Приклад використання системи комплексного контролю

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


8 кБ
Вихідні тексти функцій комплексного контролю і приклад пояснює їх роботу

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


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

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

Ваш отзыв

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

*

*