Проверка на ошибку на делфи

Июль 26th, 2013 admin

Процедура Assert используется в Delphi при отладке программы на предмет истинности утверждений. Эти утверждения по смыслу должны быть истинны, но почему-то могут нарушаться.

procedure Assert (expr : Boolean [; const msg : string]);
Если проверяемое утверждение будет ложным, то процедура прекратит работу и сгенерирует исключение EAssertionFailed с выдачей ошибки в сообщении. На входе у процедуры параметр expr, который проверяется на истинность. Также, в строку может передаться строка msg – строка сообщения. Исключение генерируется со стандартной строкой сообщения, если строка в вызове процедуры не указана. Если Вам понадобится современный и надежный платежный агрегатор интернет платежей net2pay.ru обладает простой интеграцией и удобным интерфесом, который позволяет вести свой бизнес в любой точке мира. Сообщение отображается с указанием имени файла и номером  строки, в которой возникает ошибка.

Пример:
 Assert (A <>nil, ‘Внимание A=nil !!!’);
Процедура Assert работает при включенной директиве {$C+} или {$ASSERTIONS ON}. В случае, если сообщение не было перехвачено процедурой, программа завершится с кодом 227. В итоговой программе, которая работает и отладка не требуется, следует отключить директивы {$C-} или {$ASSERTIONS OFF}. При этом компиляция проверок не производится, и в отлаженной программе не остается ничего лишнего.

.

delphi assert
assert delphi
Assert delphi
дельфи Assert
assert pascal

Комментирование и размещение ссылок запрещено.

Информатика и образование

 

Мобильная
версия сайта
                            [Главная] [Новости]                     [Архив
новостей]
                          [Форум] на
форуме можно задать вопрос, посмотреть ответы на часто задаваемые вопросы
            Здравствуйте!
Вы попали на информационно-образовательный сайт посвященный информатике,
информационным технологиям и компьютерным играм. Подробнее о целях и задачах
сайта в разделе Главная.
[English version of this page here…]
              [Базовые
уроки по DirectX] [
Основы
DirectMusic на Delphi
]
[Основы
DirectInput8 на Delph
i]
[Основы
DirectSound8 на Delphi]
      [Разработка
компьютерной игры]
[Пример
игры Donuts3D] [Delphi DirectX]
              Эмулятор
электронной игры Электроника ИМ-02 «Ну,
Погоди!»
              триал-версия, 1,34 Mb

 

Мобильная
версия сайта
                            [Главная] [Новости]                     [Архив
новостей]
                          [Форум] на
форуме можно задать вопрос, посмотреть ответы на часто задаваемые вопросы
            Здравствуйте!
Вы попали на информационно-образовательный сайт посвященный информатике,
информационным технологиям и компьютерным играм. Подробнее о целях и задачах
сайта в разделе Главная.
[English version of this page here…]
              [Базовые
уроки по DirectX] [
Основы
DirectMusic на Delphi
]
[Основы
DirectInput8 на Delph
i]
[Основы
DirectSound8 на Delphi]
      [Разработка
компьютерной игры]
[Пример
игры Donuts3D] [Delphi DirectX]
              Эмулятор
электронной игры Электроника ИМ-02 «Ну,
Погоди!»
              триал-версия, 1,34 Mb   скачайте
полную версию игры, зарегистрируйтесь
и получите бесплатно полный исходный код игры для компиляции в delphi 7
или 2006 и уроки delphi directx
8.1 содержащие статьи по созданию собственной 2D/3D игры в среде delphi
directx
              Урок
9

Тестирование и отладка приложения
              О
том, что потребуется для данного урока читайте в основном разделе — уроки
delphi directx 8.1
              Далее
у читателя подразумевается наличие базовых знаний языка Delphi.
              [назад]
[страница 1] [далее]
[к содержанию]

Мы
добрались до логического завершения уроков по созданию 2D-игры. В первых
уроках Вам давались довольно детальные пояснения, в последующих упор был
сделан на конкретных практических вопросах, а со всеми деталями Вы могли
ознакомиться в исходном коде.

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

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

             

Прежде
всего тестирование и отладка любой игры необходимы, чтобы выявить явные
и скрытые ее дефекты вызванные логическими и иными ошибками в коде.

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

             

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

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

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

Прежде
чем приступить к написанию кода нужно четко сформулировать алгоритм, который
Вы хотите реализовать. Это позволит избежать дополнительных ошибок. Новички
в создании игр часто думают: всё уже давно придумано до нас, так чего
тут думать — нужно сразу же приступать к кодированию и очень ошибутся
при этом.

Я
согласен с Вами, что всё действительно уже придумано, но знаний о том,
как это работает, у Вас может и не быть. Поэтому нужно сначала разобраться
в созданном, а потом уже и придумывать что-то своё, новое.

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

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

Создание
Вашего игрового проекта может выглядеть так:

1)
Написание шаблона стартового приложения;

2)
Добавление в шаблон вывода титульной заставки;

3)
Реализация экрана главного меню (пока без реализации возможностей отдельных
его пунктов);

4)
Последовательная реализация пунктов меню, начиная с простых;

5)
Реализация загрузки ресурсов и начального экрана игры;

6)
Постепенная реализация игровой логики и всех остальных возможностей;

             

Начав
с простого и постепенно дополняя и расширяя возможности в конце концов
достигнете своей цели создав законченный проект.

Под
тестированием и отладкой понимается всесторонняя проверка созданного приложения,
главным образом соответствие его работы задачам, которые ставились.

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

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

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

             

Попробуем
выполнить наше приложение в режиме отладки. Откройте любой проект и нужный
его модуль.

Путем
щелчка левой кнопки мыши в левом поле напротив окна текста исходного кода
установите в строке кода, которая является исполнимой контрольную точку
останова (Break Point)

             

              запустите
приложение в режиме отладки щелкнув по кнопке Run —
                            запустите
приложение в режиме отладки щелкнув по кнопке Run —
             
              или
выбрав пункт меню Run > Run

При
этом приложение будет запущено в режиме отладки, позволяющем исследовать
код путем пошагового его исполнения.

Весь
код до точки останова будет выполнен сразу же, а в точке останова исполнение
сделает паузу и отладчик будет ожидать Ваших дальнейших действий. Вы можете
нажимать клавиши [F8], [F7] — пункты меню Run > Step Over и Run >
Trace Into для пошагового исполнения каждой строки кода начиная с точки
останова.

При
этом в отдельных окнах Watch List и Local Variables, а также путем наведения
указателя мыши на любую нужную переменную Вы можете увидеть значение,
которое она принимает в данный момент.

             

Окно
Call Stack позволяет увидеть последовательность вызовов различных функций
в Вашей программе начиная снизу и вверх, в окне Event Log вы можете увидеть
события которые происходят во время исполнения и отладочные сообщения,
если таковые имеют место. Экстренно завершить выполнение приложения можно
нажав комбинацию клавиш [Ctrl] + [F2].

Это
вызовет закрытие приложения и переведет среду Delphi в обычный режим работы
над исходным кодом.

             

В
режиме отладки клавиша [F8] позволяет перейти к исполнению следующей строки
кода, [F7] позволяет войти в код функции и проследить, что происходит
внутри неё (если исполнимая строка кода является вызовом функции, процедуры
или метода).

Контрольные
точки останова нужно ставить только в тех строках кода, которые являются
исполнимыми (напротив них при компиляции появляется синяя жирная точка),
иначе это не приведет к останову в данной строке при запуске в режиме
отладки. Комментарии не являются исполнимыми строками кода, т.к. не дают
исполнимого кода при компиляции. Если Вы работаете в среде Delphi 7, то
заметите, что операторы логических скобок begin и end также не являются
исполнимыми и их нельзя использовать в качестве контрольных точек останова.
В среде Delphi 2006 это делать можно.

             

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

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

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

Например,
таким способом отображается FPS. В окончательном отлаженном проекте этот
код позднее комментируется или удаляется, так что экран в конечном счете
не будет засорен для пользователя посторонней информацией.

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

             

С
таким кодом Вы наверняка уже сталкивались в наших предыдущих уроках, и он
для Вас уже не нов, поэтому я не вижу большого смысла его здесь приводить.
              В
заключение скажу лишь, что режим отладки не является чем-то особо сложным
и недоступным пониманию, если Вы пока еще не совсем с ним хорошо знакомы.
Вам достаточно лишь раз посмотреть как происходит работа в этом режиме и
думаю, все вопросы разрешатся постепенно по мере Вашей работы.
              Кроме
того, в ходе практической реализации самостоятельного игрового проекта у
Вас будут возникать новые вопросы и если Вы будете разрешать их самостоятельно,
используя документацию по Delphi, DirectX и некоторые сведения почерпнутые
из данных уроков, то у Вас всё получится.
              Желаю
Вам всяческих успехов.

свои
вопросы Вы можете задавать

на
megainformatic@mail.ru
или
оставьте сообщение на форуме

                     

[назад]
[страница 1] [далее]
[к содержанию]
                                             
по всем вопросам пишите на
megainformatic@mail.ru
или
оставьте сообщение на форуме

Обновления
и новости о развитии Delphi DirectX проекта
смотри на сайтах:

http://www.megainformaticsite.pochta.ru


http://www.megainformatic.boom.ru

http://www.megainformatic.narod.ru                    

Cвои
пожелания, вопросы или заметки отправляйте на:

megainformatic@mail.ru или
пишите на форуме

           

Обмен ссылками

                                       

(с)
МЕГА ИНФОРМАТИК 2006-2009

Nbsp;

Методические указания для курсового проектирования по дисциплине

Оформление технического задания

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

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

В простом проекте предполагается использовать только одномерные массивы (векторы), один уровень иерархии классов и одну таблицу предметной области.

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

Сложный проект дополнительно предполагает использование собственных свойств типа массив, запись или класс, а так же редакторов этих свойств. Таблицы предметной области должны быть организованы в виде локальной базы данных с использованием представлений (форм) и простейших запросов.

Анализ предметной области и концептуальное проектирование таблиц

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

Проектирование структуры модулей и форм приложения

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

Проектирование функциональных модулей и форм

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

Реализация приложения в среде Delphi

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

Для усложненного и сложного вариантов проектов предполагается использование интегрированных сред Builder C++ и Visual C++.

Отладка и функциональное тестирование приложения

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

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

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


    1. Виды ошибок

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

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

Следующей
разновидностью можно назвать ошибки,
связанные с неправильным применением
различных конструкций Delphi. Приложение
работает, но делает “не то, что надо”
(окна открываются не так или не там, где
хотелось бы, отдельные компоненты не
работают и т.д.). В таком случае придется
просто более внимательно изучать
свойства применяемых объектов и
разобраться в правилах их использования
в своих разработках.

Как
и в других языках программирования, так
и при работе в Delphi могут встречаться
логические ошибки, когда результаты
выполнения не соответствуют ожидаемым.
Для проверки приложений всегда необходимо
проводить тестирование и отладку.
Тестирование
это запуск
программы на разных наборах исходных
данных и анализ полученных результатов.
Отладка –

это выявление и устранение причин
ошибок, если их наличие установлено
тестированием. Иногда встречается
термин испытание
программы,

под этим обычно подразумевают определение
эксплуатационных характеристик и оценка
их соответствия техническому заданию
(по времени выполнения, скорости реакции,
объему требуемой памяти . . .).

Рассмотрим
следующие отладочные средства:

  • трассировка;

  • окна
    просмотра;

  • точки
    прерывания (Breakpoints; иногда в литературе
    их называют контрольными точками).

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

  • F7
    выполнение программы по строкам, в том
    числе внутри процедур и функций;

  • F8
    выполнение программы по строкам, но
    без входа в процедуры и функции, строка
    с их вызовом рассматривается как один
    оператор;

  • F4
    выполнение программы до курсора.

Трассировка,
как правило, бессмысленна без окон
просмотра
:
в них видны значения переменных. Для
включения переменной в окно просмотра
необходимо поставить курсор на нее и
нажать комбинацию клавиш Ctrl+F5. Внешний
вид окна просмотра показан на рис. 4.1. В
нем имеется две переменных per и s1 до
запуска программы, поэтому их значения
не определены.

Окно
Call Stack позволяет увидеть последовательность вызовов различных функций
в Вашей программе начиная снизу и вверх, в окне Event Log вы можете увидеть
события которые происходят во время исполнения и отладочные сообщения,
если таковые имеют место. Экстренно завершить выполнение приложения можно
нажав комбинацию клавиш [Ctrl] + [F2].

Это
вызовет закрытие приложения и переведет среду Delphi в обычный режим работы
над исходным кодом.

             

В
режиме отладки клавиша [F8] позволяет перейти к исполнению следующей строки
кода, [F7] позволяет войти в код функции и проследить, что происходит
внутри неё (если исполнимая строка кода является вызовом функции, процедуры
или метода).

Контрольные
точки останова нужно ставить только в тех строках кода, которые являются
исполнимыми (напротив них при компиляции появляется синяя жирная точка),
иначе это не приведет к останову в данной строке при запуске в режиме
отладки. Комментарии не являются исполнимыми строками кода, т.к. не дают
исполнимого кода при компиляции. Если Вы работаете в среде Delphi 7, то
заметите, что операторы логических скобок begin и end также не являются
исполнимыми и их нельзя использовать в качестве контрольных точек останова.
В среде Delphi 2006 это делать можно.

             

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

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

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

Например,
таким способом отображается FPS. В окончательном отлаженном проекте этот
код позднее комментируется или удаляется, так что экран в конечном счете
не будет засорен для пользователя посторонней информацией.

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

             

С
таким кодом Вы наверняка уже сталкивались в наших предыдущих уроках, и он
для Вас уже не нов, поэтому я не вижу большого смысла его здесь приводить.
              В
заключение скажу лишь, что режим отладки не является чем-то особо сложным
и недоступным пониманию, если Вы пока еще не совсем с ним хорошо знакомы.
Вам достаточно лишь раз посмотреть как происходит работа в этом режиме и
думаю, все вопросы разрешатся постепенно по мере Вашей работы.
              Кроме
того, в ходе практической реализации самостоятельного игрового проекта у
Вас будут возникать новые вопросы и если Вы будете разрешать их самостоятельно,
используя документацию по Delphi, DirectX и некоторые сведения почерпнутые
из данных уроков, то у Вас всё получится.
              Желаю
Вам всяческих успехов.

свои
вопросы Вы можете задавать

на
megainformatic@mail.ru
или
оставьте сообщение на форуме

                     

[назад]
[страница 1] [далее]
[к содержанию]
                                             
по всем вопросам пишите на
megainformatic@mail.ru
или
оставьте сообщение на форуме

Обновления
и новости о развитии Delphi DirectX проекта
смотри на сайтах:

http://www.megainformaticsite.pochta.ru


http://www.megainformatic.boom.ru

http://www.megainformatic.narod.ru                    

Cвои
пожелания, вопросы или заметки отправляйте на:

megainformatic@mail.ru или
пишите на форуме

           

Обмен ссылками

                                       

(с)
МЕГА ИНФОРМАТИК 2006-2009

Nbsp;

Методические указания для курсового проектирования по дисциплине

Оформление технического задания

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

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

В простом проекте предполагается использовать только одномерные массивы (векторы), один уровень иерархии классов и одну таблицу предметной области.

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

Сложный проект дополнительно предполагает использование собственных свойств типа массив, запись или класс, а так же редакторов этих свойств. Таблицы предметной области должны быть организованы в виде локальной базы данных с использованием представлений (форм) и простейших запросов.

Анализ предметной области и концептуальное проектирование таблиц

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

Проектирование структуры модулей и форм приложения

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

Проектирование функциональных модулей и форм

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

Реализация приложения в среде Delphi

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

Для усложненного и сложного вариантов проектов предполагается использование интегрированных сред Builder C++ и Visual C++.

Отладка и функциональное тестирование приложения

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

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

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


    1. Виды ошибок

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

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

Следующей
разновидностью можно назвать ошибки,
связанные с неправильным применением
различных конструкций Delphi. Приложение
работает, но делает “не то, что надо”
(окна открываются не так или не там, где
хотелось бы, отдельные компоненты не
работают и т.д.). В таком случае придется
просто более внимательно изучать
свойства применяемых объектов и
разобраться в правилах их использования
в своих разработках.

Как
и в других языках программирования, так
и при работе в Delphi могут встречаться
логические ошибки, когда результаты
выполнения не соответствуют ожидаемым.
Для проверки приложений всегда необходимо
проводить тестирование и отладку.
Тестирование
это запуск
программы на разных наборах исходных
данных и анализ полученных результатов.
Отладка –

это выявление и устранение причин
ошибок, если их наличие установлено
тестированием. Иногда встречается
термин испытание
программы,

под этим обычно подразумевают определение
эксплуатационных характеристик и оценка
их соответствия техническому заданию
(по времени выполнения, скорости реакции,
объему требуемой памяти . . .).

Рассмотрим
следующие отладочные средства:

  • трассировка;

  • окна
    просмотра;

  • точки
    прерывания (Breakpoints; иногда в литературе
    их называют контрольными точками).

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

  • F7
    выполнение программы по строкам, в том
    числе внутри процедур и функций;

  • F8
    выполнение программы по строкам, но
    без входа в процедуры и функции, строка
    с их вызовом рассматривается как один
    оператор;

  • F4
    выполнение программы до курсора.

Трассировка,
как правило, бессмысленна без окон
просмотра
:
в них видны значения переменных. Для
включения переменной в окно просмотра
необходимо поставить курсор на нее и
нажать комбинацию клавиш Ctrl+F5. Внешний
вид окна просмотра показан на рис. 4.1. В
нем имеется две переменных per и s1 до
запуска программы, поэтому их значения
не определены.

Рис.
4.1. Окно просмотра

Для
трассировки программы вместо ее обычного
запуска нажимаем на клавишу F7: выполняется
трансляция и приложение останавливается
на первом исполняемом операторе. При
каждом нажатии на F7 выполняется только
одна строка программы и изменения
значений переменных отражаются в окне
просмотра. Во время трассировки текущая
строка выделена синей полосой (строка,
на которой находится полоса еще не
выполнена!). Для наглядности трассировки
целесообразно разместить не более
одного оператора в строке, а оператор
if . . .then . . .else разместить на трех строках,
чтобы был виден результат проверки
условия. Если программа не содержит
процедур и функций, то клавиши F7 и F8
ничем не отличаются. Клавишей F8 лучше
пользоваться, если вызываемые процедуры
уже отлажены и их построчное выполнение
лишь затруднит слежение за логикой
программы в целом.

На
практике трассировку целесообразно
выполнять следующим образом. Определить,
какую часть программы необходимо
проверять. В качестве такой части можно
выбрать процедуру, выполняемую при
выборе пункта меню или при нажатии
кнопки. Перед первым исполняемым
оператором этой процедуры поставим
курсор и нажмем на клавишу F4. Приложение
запускается и остановится на курсоре.
Дальнейшую трассировку выполняем
клавишами F7 или F8. Если установим курсор
в процедуре, которая соответствует
пункту меню, то приложение запускается
как обычно и остановится в ожидании
выбора из меню. После выбора того пункта,
реализацию которого необходимо проверить,
происходит остановка на курсоре и можно
приступать к трассировке. Если с самого
начала пользоваться клавишей F7 (или
F8), то приложение остановится в ожидании
выбора из меню, а после выбора будет
работать до конца, потому что в вызываемой
из меню процедуре нет причин, заставляющих
ее остановиться. Таким образом, по
очереди можно проверять процедуры всех
пунктов меню и кнопок. Трассировка
особенно полезна для проверки разветвлений
и циклов. В разветвлениях можно установить,
почему она выполнена неправильно:
неправильно написано условие или
неправильные данные. В циклах можно
следить за постепенным формированием
результата, за промежуточными данными,
за используемыми и получаемыми на каждом
шагу значениями, а также обнаружить
причину зацикливания, если такое имеет
место. Если в ходе пошагового выполнения
ошибку удалось обнаружить, то процесс
можно в любой момент остановить выбором
подпункта Reset из меню Run.

Точки
прерываний

предоставляют аналогичный способ
проверки приложения. Сначала надо
определить, в каких местах программы
целесообразно анализировать промежуточные
результаты и расставить по ним точки
прерываний. Для установки точки прерывания
поставим курсор на нужную строку, войдем
в меню Delphi, выберем пункт Run и подпункт
Add Breakpoint. Точка прерывания выделяется
красной полосой. Количество точек
прерывания в принципе не ограничено.
Можно установить и условные точки
прерывания. После выбора пункта меню
Add Breakpoint появляется диалоговое окно, в
котором имеется строка Condition, в эту
строку можно писать условия останова
на точке прерывания. Конечно, значения
всех переменных, участвующих в этом
условии, должны быть определены.

При
запуске приложения оно выполняется до
первой точки прерывания, при повторном
запуске до второй точки и т.д. до конца.
Естественно, что если точка прерывания
не оказалась на данном пути выполнения
программы, то она игнорируется. После
остановки программы на точке прерывания
можно дальше выполнять ее по строкам,
можно добавить переменные в окно
просмотра. Имейте в виду, что если точка
прерывания установлена внутри цикла,
то прерывания происходят на каждом шаге
выполнения цикла. Когда отладка закончена,
то для удаления точек прерываний войдите
в пункт меню View, выберите подпункт View
Breakpoints: откроется окно Breakpoints List, в котором
перечислены все точки прерывания.
Нажатием правой клавиши мыши на этом
окне откроется меню, с помощью которого
можно их удалить.

В
Delphi имеются и другие, более сложные в
применении отладочные средства.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Тестирование и отладка

Теперь давайте на минутку забудем об анализе быстродействия алгоритмов и немного поговорим о процедурных алгоритмах — алгоритмах, предназначенных для выполнения процесса разработки, а не вычислений.

Независимо от того, как мы пишем код, в какой-то момент потребуется провести тестирование [31], дабы убедиться в том, что код работает, как задумывалось. Дает ли код правильные результаты для определенного набора входных значений? Записываются ли данные в базу данных при нажатии на кнопку ОК? Естественно, если тестирование проходит неудачно, необходимо найти ошибку и устранить ее. Этот процесс известен как отладка — тестирование показало наличие ошибки и нужно найти ее и устранить. Таким образом, тестирование и отладка неразрывно связаны между собой — по сути, это две стороны одной медали.

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

Вот первое золотое правило: код всегда содержит ошибки. И в этом нет причин для стыда. Код с ошибками — это часть нормальной работы любого программиста. Нравится вам это или нет, но программисты склонны делать ошибки. Одно из удовольствий программирования и заключается в обнаружении и устранении самых неуловимых ошибок.

—-

Правило № 1. Код всегда содержит ошибки.

—-

И хотя выше было сказано, что нечего смущаться при обнаружении кода с ошибками, есть все же одна ситуация, когда это выставляет вас в плохом свете — когда вы не протестировали код в достаточном объеме.

Утверждения

Поскольку первое правило гласит, что отладку нужно проводить всегда, а вывод подразумевает, что не хотелось бы краснеть за недостаточный объем тестирования кода, необходимо научиться создавать защищенные программы. И первым средством из защитного арсенала является утверждение.

Утверждение — это программная проверка, предназначенная для определения того, выполняется ли некоторое условие или нет. Если условие, несмотря на ваши ожидания, не выполняется, возникает исключение и на экране появляется диалоговое окно, в котором объясняется, в чем состоит проблема. Диалоговое окно — это предупреждающий сигнал о том, что либо ваше предположение было ошибочным, либо код в некоторых случаях работает не так, как ожидалось. Проверки утверждений должны привести прямо к той части кода, где содержится ошибка. Утверждения представляют собой основной элемент защитного программирования: если они присутствует в коде, значит, вы однозначно говорите, что для дальнейшего выполнения кода в выбранной точке какое-то условие должно соблюдаться.

Джон Роббинс (John Robbins) [19] установил второе правило: «Утверждения, утверждения и еще раз утверждения». В соответствии с его книгой, он считает количество утверждений достаточным, если коллеги начинают жаловаться, что при вызове его кода они постоянно получают сообщения о проверках утверждений.

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

—-

Правило № 2. Используйте утверждения много и часто.

—-

К сожалению, некоторые программисты при использовании утверждений столкнутся с проблемами. Дело в том, что поддерживаемые компилятором утверждения появились только в версии Delphi 3. С тех пор утверждения можно применять безнаказанно. Компилятор позволяет указывать, вносить ли проверки в выполняемый файл или же игнорировать их. При тестировании и отладке компиляция выполняется с утверждениями. При создании окончательного выполняемого файла утверждения игнорируются.

В Delphi1 и Delphi 2 приходится применять другие способы. Существует два метода. Первый — написать метод Assert, реализация которого должна быть пустой при создании окончательной версии приложения и будет содержать проверку с вызовом Raise в противном случае. Пример такой процедуры утверждения приведен в листинге 1.7.

Листинг 1.7. Процедура утверждения для Delphi1 и Delphi 2

procedure Assert(aCondition : boolean; const aFailMsg : string);

begin

{$IFDEF UseAssert}

if not aCondition then

raise Exception.Create(aFailMsg);

{$ENDIF}

end;

Как видно из листинга, применяется директива $IFDEF компилятора. Несмотря на то что приведенная процедура проста в использовании и легко может быть вызвана из основного кода, она подразумевает, что в окончательной версии приложения будет присутствовать вызов пустой процедуры. Альтернативным вариантом организации проверки утверждений может быть использование оператора $IFDEF не в процедуре, а в основном коде при вызове процедуры. В таком случае блоки проверки будут выглядеть следующим образом:

{$IFDEF UseAssert}

Assert (MyPointer <> nil, «MyPointer should be allocated by now»);

{$ENDIF}

MyPointer^.Field := 0;

Преимущество последнего метода заключается в том, что процедура в окончательном варианте выполняемого файла отсутствует совсем. Поскольку в настоящей книге все коды предназначены для компиляции на всех версиях Delphi, используется процедура Assert, код которой показан в листинге 1.7.

Проверка утверждений может применяться тремя способами: предусловие, постусловие и инвариант. Предусловие (pre-condition) — это утверждение, находящееся в начале функции. Оно однозначно указывает, какое условие, касающееся окружения и входных параметров, должно соблюдаться перед выполнением функции. Например, предположим, что создана функция, которой в качестве параметра передается объект. При этом разработчик решил, что функции не должен передаваться nil. Помимо доведения этой информации до всех разработчиков, он должен в начале функции вставить утверждение, которое будет проверять, передается ли функции nil. В таком случае, если сам разработчик функции или кто-то из его коллег забудет о существующем ограничении, предупреждение напомнит об этом.

Постусловие (post-condition) по назначению обратно предусловию — это утверждение, находящееся в конце функции и предназначенное для проверки того, что функция была выполнена правильно. Этот тип проверки можно считать менее полезным, нежели предыдущий. В конце концов, мы программируем, подразумевая, что код будет выполняться правильно. Если при проверке постусловия возникает исключение, оставшаяся часть функции пропускается.

И последний тип утверждений — инвариант (invariant). Он представляет собой нечто среднее между предусловием и постусловием. Это утверждение, которое находится в середине кода и гарантирует, что определенная часть функции выполняется корректно.

Единственной проблемой, связанной с утверждениями, является выбор случая, когда они более предпочтительны, чем «нормальные» исключения. Это малоисследованная область, но мы постараемся охватить и ее. В общем случае, ошибки, обнаруживаемые при тестировании, можно разбить на два типа: ошибки программиста и ошибки входных данных. Давайте попытаемся разобраться, в чем же состоит отличие между этими двумя типами.

Классическим примером может служить исключение «List index is out of bounds» (Индекс в списке вышел за допустимые пределы), особенно тот случай, когда используется индекс -1. Ошибка подобного типа вызвана тем, что программист не проверяет индекс элемента перед тем, как записать или считать его из TList. Код объекта TList проверяет все передаваемые ему индексы элементов. Если индекс находится вне допустимого диапазона, возникает исключение. Пользователь приложения не может вызвать такую ошибку (по крайней мере, это покажется глубоко бессмысленным для большинства пользователей). Ошибка возникает исключительно из-за недостаточного объема проведенного тестирования. По мнению автора, это исключение должно быть утверждением.

А теперь рассмотрим другой случай. Предположим, что мы разрабатываем функцию, которая должна разворачивать данные из файла, например, из архива. Формат сжатого файла достаточно сложен, по крайней мере, он считается простой последовательностью битов, и все последовательности выглядят примерно одинаково. Если в последовательности битов функция разархивирования встретит ошибку (например, последовательность исчерпана, но логически она не закончилась), будет это утверждением или же исключением? По мнению автора, это должно быть простым исключением. Вполне вероятно, что функции в качестве входных данных будут переданы поврежденные файлы или файлы, которые не являются архивами в требуемом формате. Очевидно, что это ошибка не программиста. Она полностью вызвана внешними условиями.

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

Читайте также

Глава 15 Отладка

Глава 15
Отладка
Имеется множество правил, начиная с логики программы и расположения данных, через организацию и расположение кода и кончая реализацией, которые могут минимизировать ошибки и проблемы. Мы рекомендуем вам изучить их; найдите хорошие книги по

Отладка

Отладка
Один из худших сеансов отладки за всю мою карьеру случился в 1972 году. Терминалы, подключенные к бухгалтерской системе профсоюза грузоперевозчиков, зависали один-два раза в день. Сознательно воспроизвести ошибку было невозможно. Ошибка не отдавала предпочтений

22.1. Ошибки и отладка

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

2.2.11. Отладка CGI

2.2.11. Отладка CGI
Страницу HTML с результатом, сгенерированную по запросу мы модем увидеть выполнив CGI приложение. Для этого требуется (персональный) Web сервер. По этому я написал небольшую программу отладки, используя Delphi 2.01 и NetManage HTML

2.6 Отладка драйверов

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

4-й шаг. Отладка

4-й шаг. Отладка
По мере усложнения программ вероятность появления ошибок в работе (ошибок выполнения) растет экспоненциально. После обнаружения такой ошибки главной задачей становится выявление причины ошибки и устранение проблемы. Одним словом, это отладка.В небольших

Нагрузочное тестирование, продолжительное тестирование и тестирование стабильности

Нагрузочное тестирование, продолжительное тестирование и тестирование стабильности
Команда тестирования создает и выполняет продолжительные тесты на физическом оборудовании в лаборатории. Не забыть про внедрение неисправностей (fault

Глава 16. Тестирование и отладка

Глава 16. Тестирование и отладка

Неполадки в блоке АЕ-35. В ближайшие семьдесят два часа блок может отказать.
Артур Кларк, «Космическая Одиссея 2001 года»
Тестирование — вещь важная. Все компетентные программисты об этом знают, хотя не всегда этот вопрос стоит для них на

19.3.2. Тестирование и отладка приложений Rails

19.3.2. Тестирование и отладка приложений Rails
В Rails встроена серьезная поддержка тестирования. Обратите внимание на каталог test, который создается для каждого нового приложения. Он заполняется по мере развития приложения; вы можете (и должны) добавлять тесты по ходу создания

2. Отладка и трассировка.

2. Отладка и трассировка.
Ну это совсем банально. Выносим определение операторов за определение класса и ставим там точку останова. Чтобы не тормозило в релиз версии, окружаем слово inline ифдефами.template ‹class T›#ifndef DEBUGinline#endifSmartPointer‹T›::operator T*() { return tObj;}template ‹class T›#ifndef

Отладка приложений в eVB

Отладка приложений в eVB
Отладка приложений в eVB довольно проста. После нажатия кнопки

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

5.1.7. Отладка

5.1.7. Отладка
Команда ipcs выдает информацию о взаимодействии процессов, включая сведения о совместно используемых сегментах (для этого следует задать флаг -m). Например, в показанном ниже случае сообщается о том, что используется один такой сегмент, с номером 1627649:% ipcs -m———

Отладка

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

8.4. Отладка

8.4. Отладка
Когда программа не делает того, чего от нее ждут, главной проблемой становится отыскание ошибки (или ошибок). Всегда легче найти ошибку в какой-нибудь части программы (или в отдельном модуле), чем во всей программе. Поэтому следует придерживаться следующего

Отладка

Отладка
Инструкция отладки является средством условной компиляции. Она записывается так:debug instruction; instruction; … endВ файле управления (Ace-файле) для каждого класса можно включить или отключить параметр debug. При его включении все инструкции отладки данного класса выполняются,

Предложите, как улучшить StudyLib

(Для жалоб на нарушения авторских прав, используйте

другую форму
)

Ваш е-мэйл

Заполните, если хотите получить ответ

Оцените наш проект

1

2

3

4

5


Комплексный контроль за качеством кода


Оформил: DeeCo

Автор: Николай Мазуркин

Оглавление

  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 обычно применяется в следующих случаях:

  • в начале процедуры или функции для проверки правильности переданных
    аргументов;
  • в начале процедуры или функции для проверки правильности внутренних
    переменных;
  • в конце работы алгоритма для проверки правильности работы алгоритма;
  • для проверки правильности выполнения «надежных» функций, то есть тех
    функций, которые всегда должны выполняться успешно всегда, и их
    невыполнение рассматривается как фатальная ошибка программы. Хороший
    пример — функция CloseHandle вызываемая с верным дескриптором.
    Практически, можно не сомневаться в правильности выполнения этой
    функции, однако результат ее выполнения все-таки можно и нужно
    проверить. Подробнее об этом в главе «Категории
    ошибок».

В любом из этих случаев невыполнение передаваемого
в процедуру 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
нажмите клавиши <CTRL>+<SHIFT>+<G> и вы получите строку
похожую на эту: [‘{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);
except
  ShowMessage('Невозможно открыть файл данных');
  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 then
    ErrorMsg := 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 then
    HandleError('Фатальная ошибка ' + Mark + #13#13+
     PChar(Exception(ExceptObject).Message))
  else
    HandleError('Фатальная ошибка ' + Mark);
  // Следующая строчка никогда не выполнится, она нужна для успокоения компилятора
  Result := nil;
end;

Инициализация модуля

Создание и настройка глобального обработчика
исключения на событии TApplication.OnException.

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

end.

8. Пример использования системы комплексного
контроля

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

Исходные
тексты функций комплексного контроля и пример поясняющий их работу (8
кБ)

Обработка исключительных ситуаций в Delphi

Содержание

    Обзор
    Структурная обработка исключительных ситуаций
    Модель исключительных ситуаций в Delphi
    Синтаксис обработки исключительных ситуаций
    Примеры обработки исключительных ситуаций
    Вызов исключительной ситуации
    Доступ к экземпляру объекта exception
    Предопределенные обработчики исключительных ситуаций
    Исключения, возникающие при работе с базами данных
    Заключение

Обзор

С целью поддержки структурной обработки исключительных ситуаций (exception) в Delphi введены новые расширения языка Pascal. В данной статье будет дано описание того, что из себя представляет такая обработка, почему она полезна, будут приведены соответствующий синтаксис Object Pascal и примеры использования исключительных ситуаций в Delphi.

Структурная обработка исключительных ситуаций

Структурная обработка исключительных ситуаций — это система, позволяющая программисту при возникновении ошибки (исключительной ситуации) связаться с кодом программы, подготовленным для обработки такой ошибки. Это выполняется с помощью языковых конструкций, которые как бы «охраняют» фрагмент кода программы и определяют обработчики ошибок, которые будут вызываться, если что-то пойдет не так в «охраняемом» участке кода. В данном случае понятие исключительной ситуации относится к языку и не нужно его путать с системными исключительными ситуациями (hardware exceptions), такими как General Protection Fault. Эти исключительные ситуации обычно используют прерывания и особые состояния «железа» для обработки критичной системной ошибки; исключительные ситуации в Delphi же независимы от «железа», не используют прерываний и используются для обработки ошибочных состояний, с которыми подпрограмма не готова иметь дело. Системные исключительные ситуации, конечно, могут быть перехвачены и преобразованы в языковые исключительные ситуации, но это только одно из применений языковых исключительных ситуаций.

При традиционной обработке ошибок, ошибки, обнаруженные в процедуре обычно передаются наружу (в вызывавшую процедуру) в виде возвращаемого значения функции, параметров или глобальных переменных (флажков). Каждая вызывающая процедура должна проверять результат вызова на наличие ошибки и выполнять соответствующие действия. Часто, это просто выход еще выше, в более верхнюю вызывающую процедуру и т.д. : функция A вызывает B, B вызывает C, C обнаруживает ошибку и возвращает код ошибки в B, B проверяет возвращаемый код, видит, что возникла ошибка и возвращает код ошибки в A, A проверяет возвращаемый код и выдает сообщение об ошибке либо решает сделать что-нибудь еще, раз первая попытка не удалась.

Такая «пожарная бригада» для обработки ошибок трудоемка, требует написания большого количества кода в котором можно легко ошибиться и который трудно отлаживать.

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

Структурная обработка исключительной ситуации замещает ручную обработку ошибок автоматической, сгенерированной компилятором системой уведомления. В приведенном выше примере, процедура A установила бы «охрану» со связанным обработчиком ошибки на фрагмент кода, в котором вызывается B. B просто вызывает C. Когда C обнаруживает ошибку, то создает (raise) исключительную ситуацию. Специальный код, сгенерированный компилятором и встроенный в Run-Time Library (RTL) начинает поиск обработчика данной исключительной ситуации. При поиске «защищенного» участка кода используется информация, сохраненная в стеке. В процедурах C и B нет такого участка, а в A — есть. Если один из обработчиков ошибок, которые используются в A, подходит по типу для возникшей в C исключительной ситуации, то программа переходит на его выполнение. При этом, область стека, используемая в B и C, очищается; выполнение этих процедур прекращается.

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

Без проверки возвращаемого кода после каждого вызова подпрограммы, код программы должен быть более простым, а скомпилированный код — более быстрым. При наличии исключительных ситуаций подпрограмма B не должна содержать дополнительный код для проверки возвращаемого результата и передачи его в A. B ничего не должна делать для передачи исключительной ситуации, возникшей в C, в процедуру A — встроенная система обработки исключительных ситуаций делает всю работу.

Данная система называется структурной, поскольку обработка ошибок определяется областью «защищенного» кода; такие области могут быть вложенными. Выполнение программы не может перейти на произвольный участок кода; выполнение программы может перейти только на обработчик исключительной ситуации активной программы.

Модель исключительных ситуаций в Delphi

Модель исключительных ситуаций в Object Pascal является невозобновляемой(non-resumable). При возникновении исключительной ситуации Вы уже не сможете вернуться в точку, где она возникла, для продолжения выполнения программы (это позволяет сделать возобновляемая(resumable) модель). Невозобновляемые исключительные ситуации разрушают стек, поскольку они сканируют его в поисках обработчика; в возобновляемой модели необходимо сохранять стек, состояние регистров процессора в точке возникновения ошибки и выполнять поиск обработчика и его выполнение в отдельном стеке. Возобновляемую систему обработки исключительных ситуаций гораздо труднее создать и применять, нежели невозобновляемую.

Синтаксис обработки исключительных ситуаций

Теперь, когда мы рассмотрели, что такое исключительные ситуации, давайте дадим ясную картину, как они применяются. Новое ключевое слово, добавленное в язык Object Pascal — try. Оно используется для обозначения первой части защищенного участка кода. Существует два типа защищенных участков:

  • try..except
  • try..finally

Первый тип используется для обработки исключительных ситуаций. Его синтаксис:

try
  Statement 1;
  Statement 2;
  ...
except
  on Exception1 do Statement;
  on Exception2 do Statement;
  ...
else
  Statements; {default exception-handler}
end;

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

try
  Statement1;
  Statement2;
  ...
finally
  Statements;  { These statements always execute }
end;

Примеры обработки исключительных ситуаций

Ниже приведены процедуры A,B и C, обсуждавшиеся ранее, воплощенные в новом синтаксисе Object Pascal:

type
  ESampleError = class(Exception);
var
  ErrorCondition: Boolean;
procedure C;
begin
  writeln('Enter C');
  if (ErrorCondition) then
  begin
    writeln('Raising exception in C');
    raise ESampleError.Create('Error!');
  end;
  writeln('Exit C');
end;
procedure B;
begin
  writeln('enter B');
  C;
  writeln('exit B');
end;
procedure A;
begin
  writeln('Enter A');
  try
    writeln('Enter A''s try block');
    B;
    writeln('After B call');
  except
    on ESampleError do
      writeln('Inside A''s ESampleError handler');
    on ESomethingElse do
      writeln('Inside A''s ESomethingElse handler');
  end;
  writeln('Exit A');
end;
begin
  writeln('begin main');
  ErrorCondition := True;
  A;
  writeln('end main');
end. 

При ErrorCondition = True программа выдаст:

begin main
Enter A
Enter A's try block
enter B
Enter C
Raising exception in C
Inside A's ESampleError handler
Exit A
end main

Возможно вас удивила декларация типа ‘ESampleError =class’ вместо ‘=object’; это еще одно новое расширение языка. Delphi вводит новую модель объектов, доступную через декларацию типа ‘=class’. Описание новой объектной модели дается в других уроках. Здесь же достаточно сказать, что исключительные ситуации (exceptions) являются классами, частью новой объектной модели.

Процедура C проверяет наличие ошибки (в нашем случае это значение глобальной переменной) и, если она есть (а это так), C вызывает(raise) исключительную ситуацию класса ESampleError.

Процедура A помещает часть кода в блок try..except. Первая часть этого блока содержит часть кода, аналогично конструкции begin..end. Эта часть кода завершается ключевым словом except, далее следует один или более обработчиков исключительных ситуаций on xxxx do yyyy, далее может быть включен необязательный блок else, вся конструкция заканчивается end;. В конструкции, назначающей определенную обработку для конкретной исключительной ситуации (on xxxx do yyyy), после резервного слова on указывается класс исключительной ситуации, а после do следует собственно код обработки данной ошибки. Если возникшая исключительная ситуация подходит по типу к указанному после on, то выполнение программы переходит сюда (на код после do). Исключительная ситуация подходит в том случае, если она того же класса, что указан в on, либо является его потомком. Например, в случае on EFileNotFound обрабатываться будет ситуация, когда файл не найден. А в случае on EFileIO — все ошибки при работе с файлами, в том числе и предыдущая ситуация. В блоке else обрабатываются все ошибки, не обработанные до этого.

Приведенные в примере процедуры содержат код (строка с writeln), который отображает путь выполнения программы. Когда C вызывает exception, программа сразу переходит на обработчик ошибок в процедуре A, игнорируя оставшуюся часть кода в процедурах B и C.

После того, как найден подходящий обработчик ошибки, поиск оканчивается. После выполнения кода обработчика, программа продолжает выполняться с оператора, стоящего после слова end блока try..except (в примере — writeln(‘Exit A’)).

Конструкция try..except подходит, если известно, какой тип ошибок нужно обрабатывать в конкретной ситуации. Но что делать, если требуется выполнить некоторые действия в любом случае, произошла ошибка или нет? Это тот случай, когда понадобится конструкция try..finally.

Рассмотрим модифицированную процедуру B:

procedure NewB;
var
  P: Pointer;
begin
  writeln('enter B');
  GetMem(P, 1000);
  C;
  FreeMem(P, 1000);
  writeln('exit B');
end;

Если C вызывает исключительную ситуацию, то программа уже не возвращается в процедуру B. А что же с теми 1000 байтами памяти, захваченными в B? Строка FreeMem(P,1000) не выполнится и Вы потеряете кусок памяти. Как это исправить? Нужно ненавязчиво включить процедуру B в процесс, например:

procedure NewB;
var
  P: Pointer;
begin
  writeln('enter NewB');
  GetMem(P, 1000);
  try
    writeln('enter NewB''s try block');
    C;
    writeln('end of NewB''s try block');
  finally
    writeln('inside NewB''s finally block');
    FreeMem(P, 1000);
  end;
  writeln('exit NewB');
end;

Если в A поместить вызов NewB вместо B, то программа выведет сообщения следующим образом:

begin main
Enter A
Enter A's try block
enter NewB
enter NewB's try block
Enter C
Raising exception in C
inside NewB's finally block
Inside A's ESampleError handler
Exit A
end main

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

Почему вызов GetMem не помещен внутрь блока try? Этот вызов может окончиться неудачно и вызвать exception EOutOfMemory. Если это произошло, то FreeMem попытается освободить память, которая не была распределена. Когда мы размещаем GetMem вне защищаемого участка, то предполагаем, что B сможет получить нужное количество памяти, а если нет, то более верхняя процедура получит уведомление EOutOfMemory.

А что, если требуется в B распределить 4 области памяти по схеме все-или-ничего? Если первые две попытки удались, а третья провалилась, то как освободить захваченную область память? Можно так:

procedure NewB;
var
  p,q,r,s: Pointer;
begin
  writeln('enter B');
  P := nil;
  Q := nil;
  R := nil;
  S := nil;
  try
    writeln('enter B''s try block');
    GetMem(P, 1000);
    GetMem(Q, 1000);
    GetMem(R, 1000);
    GetMem(S, 1000);
    C;
    writeln('end of B''s try block');
  finally
    writeln('inside B''s finally block');
    if P <> nil then FreeMem(P, 1000);
    if Q <> nil then FreeMem(Q, 1000);
    if R <> nil then FreeMem(R, 1000);
    if S <> nil then FreeMem(S, 1000);
  end;
  writeln('exit B');
end;

Установив сперва указатели в NIL, далее можно определить, успешно ли прошел вызов GetMem.

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

Вызов исключительной ситуации

В процедуре C из примера мы уже могли видеть, как должна поступать программа при обнаружении состояния ошибки — она вызывает исключительную ситуацию:

raise ESampleError.Create('Error!');

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

Почти все существующие классы исключительных ситуаций являются наследниками базового класса Exception и не содержат новых свойств или методов. Класс Exception имеет несколько конструкторов, какой из них конкретно использовать — зависит от задачи. Описание класса Exception можно найти в on-line Help.

Доступ к экземпляру объекта exception

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

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

Рассмотрим модифицированную процедуру A в нашем примере:

procedure NewA;
begin
  writeln('Enter A');
  try
    writeln('Enter A''s try block');
    B;
    writeln('After B call');
  except
    on E: ESampleError do writeln(E.Message);
    on ESomethingElse do
      writeln('Inside A''s ESomethingElse handler');
  end;
  writeln('Exit A');
end;

Здесь все изменения внесены в строку

    on ESE: ESampleError do writeln(ESE.Message);

Пример демонстрирует еще одно новшество в языке Object Pascal — создание локальной переменной. В нашем примере локальной переменной является ESE — это тот самый экземпляр класса ESampleError, который был создан в процедуре C в момент вызова исключительного состояния. Переменная ESE доступна только внутри блока do. Свойство Message объекта ESE содержит сообщение, которое было передано в конструктор Create в процедуре C.

Есть еще один способ доступа к экземпляру exception — использовать функцию ExceptionObject:

on ESampleError do
  writeln(ESampleError(ExceptionObject).Message);

Предопределенные обработчики исключительных ситуаций

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

  • Exception — базовый класс-предок всех обработчиков исключительных ситуаций.
  • EAbort — «скрытое» исключение. Используйте его тогда, когда хотите прервать тот или иной процесс с условием, что пользователь программы не должен видеть сообщения об ошибке. Для повышения удобства использования в модуле SysUtils предусмотрена процедура Abort, определенная, как:
  • procedure Abort;
    begin
      raise EAbort.CreateRes(SOperationAborted) at ReturnAddr;
    end; 
  • EComponentError — вызывается в двух ситуациях:
    1. при попытке регистрации компоненты за пределами процедуры Register;
    2. когда имя компоненты не уникально или не допустимо.
  • EConvertError — происходит в случае возникновения ошибки при выполнении функций StrToInt и StrToFloat, когда конвертация строки в соответствующий числовой тип невозможна.
  • EInOutError — происходит при ошибках ввода/вывода при включенной директиве {$I+}.
  • EIntError — предок исключений, случающихся при выполнении целочисленных операций.
    • EDivByZero — вызывается в случае деления на ноль, как результат RunTime Error 200.
    • EIntOverflow — вызывается при попытке выполнения операций, приводящих к переполнению целых переменных, как результат RunTime Error 215 при включенной директиве {$Q+}.
    • ERangeError — вызывается при попытке обращения к элементам массива по индексу, выходящему за пределы массива, как результат RunTime Error 201 при включенной директиве {$R+}.
  • EInvalidCast — происходит при попытке приведения переменных одного класса к другому классу, несовместимому с первым (например, приведение переменной типа TListBox к TMemo).
  • EInvalidGraphic — вызывается при попытке передачи в LoadFromFile файла, несовместимого графического формата.
  • EInvalidGraphicOperation — вызывается при попытке выполнения операций, неприменимых для данного графического формата (например, Resize для TIcon).
  • EInvalidObject — реально нигде не используется, объявлен в Controls.pas.
  • EInvalidOperation — вызывается при попытке отображения или обращения по Windows-обработчику (handle) контрольного элемента, не имеющего владельца (например, сразу после вызова MyControl:=TListBox.Create(…) происходит обращение к методу Refresh).
  • EInvalidPointer — происходит при попытке освобождения уже освобожденного или еще неинициализированного указателя, при вызове Dispose(), FreeMem() или деструктора класса.
  • EListError — вызывается при обращении к элементу наследника TList по индексу, выходящему за пределы допустимых значений (например, объект TStringList содержит только 10 строк, а происходит обращение к одиннадцатому).
  • EMathError — предок исключений, случающихся при выполнении операций с плавающей точкой.
    • EInvalidOp — происходит, когда математическому сопроцессору передается ошибочная инструкция. Такое исключение не будет до конца обработано, пока Вы контролируете сопроцессор напрямую из ассемблерного кода.
    • EOverflow — происходит как результат переполнения операций с плавающей точкой при слишком больших величинах. Соответствует RunTime Error 205.
    • Underflow — происходит как результат переполнения операций с плавающей точкой при слишком малых величинах. Соответствует RunTime Error 206.
    • EZeroDivide — вызывается в результате деления на ноль.
  • EMenuError — вызывается в случае любых ошибок при работе с пунктами меню для компонент TMenu, TMenuItem, TPopupMenu и их наследников.
  • EOutlineError — вызывается в случае любых ошибок при работе с TOutLine и любыми его наследниками.
  • EOutOfMemory — происходит в случае вызовов New(), GetMem() или конструкторов классов при невозможности распределения памяти. Соответствует RunTime Error 203.
  • EOutOfResources — происходит в том случае, когда невозможно выполнение запроса на выделение или заполнение тех или иных Windows ресурсов (например таких, как обработчики — handles).
  • EParserError — вызывается когда Delphi не может произвести разбор и перевод текста описания формы в двоичный вид (часто происходит в случае исправления текста описания формы вручную в IDE Delphi).
  • EPrinter — вызывается в случае любых ошибок при работе с принтером.
  • EProcessorException — предок исключений, вызываемых в случае прерывания процессора- hardware breakpoint. Никогда не включается в DLL, может обрабатываться только в «цельном» приложении.
    • EBreakpoint — вызывается в случае останова на точке прерывания при отладке в IDE Delphi. Среда Delphi обрабатывает это исключение самостоятельно.
    • EFault — предок исключений, вызываемых в случае невозможности обработки процессором тех или иных операций.
      • EGPFault — вызывается, когда происходит «общее нарушение защиты» — General Protection Fault. Соответствует RunTime Error 216.
      • EInvalidOpCode — вызывается, когда процессор пытается выполнить недопустимые инструкции.
      • EPageFault — обычно происходит как результат ошибки менеджера памяти Windows, вследствие некоторых ошибок в коде Вашего приложения. После такого исключения рекомендуется перезапустить Windows.
      • EStackFault — происходит при ошибках работы со стеком, часто вследствие некорректных попыток доступа к стеку из фрагментов кода на ассемблере. Компиляция Ваших программ со включенной проверкой работы со стеком {$S+} помогает отследить такого рода ошибки.
    • ESingleStep — аналогично EBreakpoint, это исключение происходит при пошаговом выполнении приложения в IDE Delphi, которая сама его и обрабатывает.
  • EPropertyError — вызывается в случае ошибок в редакторах свойств, встраиваемых в IDE Delphi. Имеет большое значение для написания надежных property editors. Определен в модуле DsgnIntf.pas.
  • EResNotFound — происходит в случае тех или иных проблем, имеющих место при попытке загрузки ресурсов форм — файлов .DFM в режиме дизайнера. Часто причиной таких исключений бывает нарушение соответствия между определением класса формы и ее описанием на уровне ресурса (например,вследствие изменения порядка следования полей-ссылок на компоненты, вставленные в форму в режиме дизайнера).
  • EStreamError — предок исключений, вызываемых при работе с потоками.
    • EFCreateError — происходит в случае ошибок создания потока (например, при некорректном задании файла потока).
    • EFilerError — вызывается при попытке вторичной регистрации уже зарегистрированного класса (компоненты). Является, также, предком специализированных обработчиков исключений, возникающих при работе с классами компонент.
      • EClassNotFound — обычно происходит, когда в описании класса формы удалено поле-ссылка на компоненту, вставленную в форму в режиме дизайнера. Вызывается, в отличие от EResNotFound, в RunTime.
      • EInvalidImage — вызывается при попытке чтения файла, не являющегося ресурсом, или разрушенного файла ресурса, специализированными функциями чтения ресурсов (например, функцией ReadComponent).
      • EMethodNotFound — аналогично EClassNotFound, только при несоответствии методов, связанных с теми или иными обработчиками событий.
      • EReadError — происходит в том случае, когда невозможно прочитать значение свойства или другого набора байт из потока (в том числе ресурса).
    • EFOpenError — вызывается когда тот или иной специфированный поток не может быть открыт (например, когда поток не существует).
  • EStringListError — происходит при ошибках работы с объектом TStringList (кроме ошибок, обрабатываемых TListError).

Исключения, возникающие при работе с базами данных

Delphi, обладая прекрасными средствами доступа к данным, основывающимися на интерфейсе IDAPI, реализованной в виде библиотеки Borland Database Engine (BDE), включает ряд обработчиков исключительных ситуаций для регистрации ошибок в компонентах VCL работающим с БД. Дадим краткую характеристику основным из них:

  • EDatabaseError — наследник Exception ; происходит при ошибках доступа к данным в компонентах-наследниках TDataSet. Объявлено в модуле DB. Ниже приведен пример из Delphi On-line Help, посвященный этому исключению:
  • repeat {пока не откроем таблицу или не нажмем кнопку  Cancel}
      try
        Table1.Active := True; {Пытаемся открыть таблицу}
        Break; { Если нет ошибки - прерваем цикл}
      except
        on EDatabaseError do
          {Если нажата OK - повторяем попытку открытия  Table1}
          if MessageDlg('Не могу открыть  Table1', mtError,
    			[mbOK, mbCancel], 0) <> mrOK
          then
            raise;
      end;
    until False;
    • EDBEngineError — наследник EDatabaseError ; вызывается, когда происходят
      ошибки BDE или на сервере БД. Объявлено в модуле DB:
    • EDBEngineError = class(EDatabaseError)
      private
        FErrors: TList;
        function GetError(Index: Integer): TDBError;
        function GetErrorCount: Integer;
      public
        constructor Create(ErrorCode: DBIResult);
        destructor Destroy;
        property ErrorCount: Integer;
        property Errors[Index: Integer]: TDBError;
      end;

      Особенно важны два свойства класса EDBEngineError : Errors
      — список всех ошибок, находящихся в стеке ошибок BDE. Индекс первой ошибки
      0;
      ErrorCount — количество ошибок в стеке.
      Объекты, содержащиеся в Errors, имеют тип TDBError. Доступные свойства класса
      TDBError:
      ErrorCode — код ошибки, возвращаемый Borland Database Engine;
      Category — категория ошибки, описанной в ErrorCode;
      SubCode — ‘субкод’ ошибки из ErrorCode;
      NativeError — ошибка, возвращаемая сервером БД. Если NativeError 0, то ошибка
      в ErrorCode не от сервера;
      Message — сообщение, переданное сервером, если NativeError не равно 0; сообщение
      BDE — в противном случае.

  • EDBEditError — наследник Exception ; вызывается, когда данные не совместимы с маской ввода, наложенной на поле. Объявлено в модуле Mask.

Заключение

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

Назад | Содержание
| Вперед

Новости мира IT:

  • 30.01 — Роскомнадзор получил более 44 тыс. жалоб о неправомерной обработке персональных данных в 2022 году
  • 30.01 — На Apple подали в суд из-за сбора данных пользователей
  • 30.01 — «Ростелеком», возможно, интересуется покупкой «Мегафона»
  • 30.01 — В идеале Apple стремится создать очки дополненной реальности, которые можно носить весь день
  • 30.01 — Российские мобильные операторы перешли на отечественную техподдержку
  • 30.01 — Продажа «Билайна» российскому топ-менеджменту затягивается
  • 27.01 — «Яндекс» попал в десятку самых посещаемых сайтов в мире
  • 27.01 — В списке кредиторов криптобиржи FTX оказались Apple, Google, Amazon, Microsoft, а также авиакомпании, СМИ и университеты
  • 27.01 — IBM и SAP показали хорошие квартальные результаты, но всё равно сокращают рабочие места
  • 27.01 — От антимонопольного иска против Google выиграет Apple и другие компании
  • 26.01 — Власти США подали иск, чтобы заблокировать сделку между Microsoft и Activision Blizzard
  • 26.01 — Apple намерена вытеснить сервисы Google из iPhone — грядут «яблочные» заменители для рекламы и даже поиска
  • 26.01 — Минюст США потребовал от Google раздробить рекламное подразделение и выплатить компенсации за завышение цен
  • 26.01 — ЕС готовит антимонопольное расследование против Microsoft из-за сервиса Teams
  • 26.01 — Российские компании активизировали наем IT-специалистов — число вакансий растёт
  • 26.01 — Создан простой для вживления мозговой имплантат, который позволит набирать текст силой мысли
  • 25.01 — Представлен стабильный релиз Wine 8.0
  • 25.01 — «Яндекс» научил нейросеть расшифровывать архивные документы даже с дореволюционной орфографией
  • 25.01 — Объём атак на российских операторов связи снизился в 2022 году
  • 25.01 — В России возникли проблемы с доступом к Apple App Store и некоторым другим сервисам

Архив новостей

  • Perfect Quality Hosting – компания со своим подходом к услугам аренды серверов
  • Создали сайт – выберите для него достойный хостинг
  • Гиперконвергентные решения: что нужно знать при переходе на них

Go Up to Classes and Objects Index

This topic covers the following material:

  • A conceptual overview of exceptions and exception handling
  • Declaring exception types
  • Raising and handling exceptions

Contents

  • 1 About Exceptions
  • 2 When To Use Exceptions
  • 3 Declaring Exception Types
  • 4 Raising and Handling Exceptions
    • 4.1 Try…except Statements
    • 4.2 Re-raising Exceptions
    • 4.3 Nested Exceptions
    • 4.4 Try…finally Statements
  • 5 Standard Exception Classes and Routines
  • 6 See Also

About Exceptions

An exception is raised when an error or other event interrupts normal execution of a program. The exception transfers control to an exception handler, which allows you to separate normal program logic from error-handling. Because exceptions are objects, they can be grouped into hierarchies using inheritance, and new exceptions can be introduced without affecting existing code. An exception can carry information, such as an error message, from the point where it is raised to the point where it is handled.

When an application uses the SysUtils unit, most runtime errors are automatically converted into exceptions. Many errors that would otherwise terminate an application — such as insufficient memory, division by zero, and general protection faults — can be caught and handled.

When To Use Exceptions

Exceptions provide an elegant way to trap runtime errors without halting the program and without awkward conditional statements. The requirements imposed by exception handling semantics impose a code/data size and runtime performance penalty. While it is possible to raise exceptions for almost any reason, and to protect almost any block of code by wrapping it in a try…except or try…finally statement, in practice these tools are best reserved for special situations.

Exception handling is appropriate for errors whose chances of occurring are low or difficult to assess, but whose consequences are likely to be catastrophic (such as crashing the application); for error conditions that are complicated or difficult to test for in if…then statements; and when you need to respond to exceptions raised by the operating system or by routines whose source code you don’t control. Exceptions are commonly used for hardware, memory, I/O, and operating-system errors.

Conditional statements are often the best way to test for errors. For example, suppose you want to make sure that a file exists before trying to open it. You could do it this way:

try
    AssignFile(F, FileName);
    Reset(F);     // raises an EInOutError exception if file is not found
except
    on Exception do ...
end;

But you could also avoid the overhead of exception handling by using:

if FileExists(FileName) then    // returns False if file is not found; raises no exception

begin
    AssignFile(F, FileName);
    Reset(F);
end;

Assertions provide another way of testing a Boolean condition anywhere in your source code. When an Assert statement fails, the program either halts with a runtime error or (if it uses the SysUtils unit) raises an SysUtils.EAssertionFailed exception. Assertions should be used only to test for conditions that you do not expect to occur.

Declaring Exception Types

Exception types are declared just like other classes. In fact, it is possible to use an instance of any class as an exception, but it is recommended that exceptions be derived from the SysUtils.Exception class defined in SysUtils.

You can group exceptions into families using inheritance. For example, the following declarations in SysUtils define a family of exception types for math errors:

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

Given these declarations, you can define a single SysUtils.EMathError exception handler that also handles SysUtils.EInvalidOp, SysUtils.EZeroDivide, SysUtils.Overflow, and SysUtils.EUnderflow.

Exception classes sometimes define fields, methods, or properties that convey additional information about the error. For example:

type EInOutError = class(Exception)
       ErrorCode: Integer;
     end;

Raising and Handling Exceptions

To raise an exception object, use an instance of the exception class with a raise statement. For example:

raise EMathError.Create;

In general, the form of a raise statement is

raise object at address

where object and at address are both optional. When an address is specified, it can be any expression that evaluates to a pointer type, but is usually a pointer to a procedure or function. For example:

raise Exception.Create('Missing parameter') at @MyFunction;

Use this option to raise the exception from an earlier point in the stack than the one where the error actually occurred.

When an exception is raised — that is, referenced in a raise statement — it is governed by special exception-handling logic. A raise statement never returns control in the normal way. Instead, it transfers control to the innermost exception handler that can handle exceptions of the given class. (The innermost handler is the one whose try…except block was most recently entered but has not yet exited.)

For example, the function below converts a string to an integer, raising an SysUtils.ERangeError exception if the resulting value is outside a specified range.

function StrToIntRange(const S: string; Min, Max: Longint): Longint;
begin
    Result := StrToInt(S);   // StrToInt is declared in SysUtils
    if (Result < Min) or (Result > Max) then
       raise ERangeError.CreateFmt('%d is not within the valid range of %d..%d', [Result, Min, Max]);
end;

Notice the CreateFmt method called in the raise statement. SysUtils.Exception and its descendants have special constructors that provide alternative ways to create exception messages and context IDs.

A raised exception is destroyed automatically after it is handled. Never attempt to destroy a raised exception manually.

Note: Raising an exception in the initialization section of a unit may not produce the intended result. Normal exception support comes from the SysUtils unit, which must be initialized before such support is available. If an exception occurs during initialization, all initialized units — including SysUtils — are finalized and the exception is re-raised. Then the exception is caught and handled, usually by interrupting the program. Similarly, raising an exception in the finalization section of a unit may not lead to the intended result if SysUtils has already been finalized when the exception has been raised.

Try…except Statements

Exceptions are handled within try…except statements. For example:

try
   X := Y/Z;
   except
     on EZeroDivide do HandleZeroDivide;
end;

This statement attempts to divide Y by Z, but calls a routine named HandleZeroDivide if an SysUtils.EZeroDivide exception is raised.

The syntax of a try…except statement is:

try statements except exceptionBlock end

where statements is a sequence of statements (delimited by semicolons) and exceptionBlock is either:

  • another sequence of statements or
  • a sequence of exception handlers, optionally followed by
else statements

An exception handler has the form:

on identifier: type do statement

where identifier: is optional (if included, identifier can be any valid identifier), type is a type used to represent exceptions, and statement is any statement.

A try…except statement executes the statements in the initial statements list. If no exceptions are raised, the exception block (exceptionBlock) is ignored and control passes to the next part of the program.

If an exception is raised during execution of the initial statements list, either by a raise statement in the statements list or by a procedure or function called from the statements list, an attempt is made to ‘handle’ the exception:

  • If any of the handlers in the exception block matches the exception, control passes to the first such handler. An exception handler ‘matches’ an exception just in case the type in the handler is the class of the exception or an ancestor of that class.
  • If no such handler is found, control passes to the statement in the else clause, if there is one.
  • If the exception block is just a sequence of statements without any exception handlers, control passes to the first statement in the list.

If none of the conditions above is satisfied, the search continues in the exception block of the next-most-recently entered try…except statement that has not yet exited. If no appropriate handler, else clause, or statement list is found there, the search propagates to the next-most-recently entered try…except statement, and so forth. If the outermost try…except statement is reached and the exception is still not handled, the program terminates.

When an exception is handled, the stack is traced back to the procedure or function containing the try…except statement where the handling occurs, and control is transferred to the executed exception handler, else clause, or statement list. This process discards all procedure and function calls that occurred after entering the try…except statement where the exception is handled. The exception object is then automatically destroyed through a call to its Destroy destructor and control is passed to the statement following the try…except statement. (If a call to the Exit, Break, or Continue standard procedure causes control to leave the exception handler, the exception object is still automatically destroyed.)

In the example below, the first exception handler handles division-by-zero exceptions, the second one handles overflow exceptions, and the final one handles all other math exceptions. SysUtils.EMathError appears last in the exception block because it is the ancestor of the other two exception classes; if it appeared first, the other two handlers would never be invoked:

try
  ...
except
  on EZeroDivide do HandleZeroDivide;
  on EOverflow do HandleOverflow;
  on EMathError do HandleMathError;
end;

An exception handler can specify an identifier before the name of the exception class. This declares the identifier to represent the exception object during execution of the statement that follows on…do. The scope of the identifier is limited to that statement. For example:

try
  ...
except
  on E: Exception do ErrorDialog(E.Message, E.HelpContext);
end;

If the exception block specifies an else clause, the else clause handles any exceptions that aren’t handled by the block’s exception handlers. For example:

try
  ...
except
  on EZeroDivide do HandleZeroDivide;
  on EOverflow do HandleOverflow;
  on EMathError do HandleMathError;
else
  HandleAllOthers;
end;

Here, the else clause handles any exception that isn’t an SysUtils.EMathError.

An exception block that contains no exception handlers, but instead consists only of a list of statements, handles all exceptions. For example:

try
   ...
except
   HandleException;
end;

Here, the HandleException routine handles any exception that occurs as a result of executing the statements between try and except.

Re-raising Exceptions

When the reserved word raise occurs in an exception block without an object reference following it, it raises whatever exception is handled by the block. This allows an exception handler to respond to an error in a limited way and then re-raise the exception. Re-raising is useful when a procedure or function has to clean up after an exception occurs but cannot fully handle the exception.

For example, the GetFileList function allocates a TStringList object and fills it with file names matching a specified search path:

function GetFileList(const Path: string): TStringList;
var
  I: Integer;
  SearchRec: TSearchRec;
begin
  Result := TStringList.Create;
  try
    I := FindFirst(Path, 0, SearchRec);
    while I = 0 do
      begin
          Result.Add(SearchRec.Name);
          I := FindNext(SearchRec);
      end;
  except
      Result.Free;
      raise;
  end;
end;

GetFileList creates a TStringList object, then uses the FindFirst and FindNext functions (defined in SysUtils) to initialize it. If the initialization fails — for example because the search path is invalid, or because there is not enough memory to fill in the string list — GetFileList needs to dispose of the new string list, since the caller does not yet know of its existence. For this reason, initialization of the string list is performed in a try…except statement. If an exception occurs, the statement’s exception block disposes of the string list, then re-raises the exception.

Nested Exceptions

Code executed in an exception handler can itself raise and handle exceptions. As long as these exceptions are also handled within the exception handler, they do not affect the original exception. However, once an exception raised in an exception handler propagates beyond that handler, the original exception is lost. This is illustrated by the Tan function below:

type
   ETrigError = class(EMathError);
   function Tan(X: Extended): Extended;
   begin
      try
        Result := Sin(X) / Cos(X);
      except
        on EMathError do
        raise ETrigError.Create('Invalid argument to Tan');
      end;
   end;

If an SysUtils.EMathError exception occurs during execution of Tan, the exception handler raises an ETrigError. Since Tan does not provide a handler for ETrigError, the exception propagates beyond the original exception handler, causing the SysUtils.EMathError exception to be destroyed. To the caller, it appears as if the Tan function has raised an ETrigError exception.

Try…finally Statements

Sometimes you want to ensure that specific parts of an operation are completed, whether or not the operation is interrupted by an exception. For example, when a routine acquires control of a resource, it is often important that the resource be released, regardless of whether the routine terminates normally. In these situations, you can use a try…finally statement.

The following example shows how code that opens and processes a file can ensure that the file is ultimately closed, even if an error occurs during execution:

Reset(F);
try
   ... // process file F
finally
   CloseFile(F);
end;

The syntax of a try…finally statement is

try statementList1 finally statementList2 end

where each statementList is a sequence of statements delimited by semicolons. The try…finally statement executes the statements in statementList1 (the try clause). If statementList1 finishes without raising exceptions, statementList2 (the finally clause) is executed. If an exception is raised during execution of statementList1, control is transferred to statementList2; once statementList2 finishes executing, the exception is re-raised. If a call to the Exit, Break, or Continue procedure causes control to leave statementList1, statementList2 is automatically executed. Thus the finally clause is always executed, regardless of how the try clause terminates.

If an exception is raised but not handled in the finally clause, that exception is propagated out of the try…finally statement, and any exception already raised in the try clause is lost. The finally clause should therefore handle all locally raised exceptions, so as not to disturb propagation of other exceptions.

Standard Exception Classes and Routines

The SysUtils and System units declare several standard routines for handling exceptions, including ExceptObject, ExceptAddr, and ShowException. SysUtils, System and other units also include dozens of exception classes, all of which (aside from OutlineError) derive from SysUtils.Exception.

The SysUtils.Exception class has properties called Message and HelpContext that can be used to pass an error description and a context ID for context-sensitive online documentation. It also defines various constructor methods that allow you to specify the description and context ID in different ways.

See Also

  • Classes and Objects (Delphi)
  • Fields (Delphi)
  • Methods (Delphi)
  • Properties (Delphi)
  • Nested Type Declarations
  • Class References
  • Operator Overloading (Delphi)
  • Class and Record Helpers (Delphi)
  • Handling Exceptions

Возможно, вам также будет интересно:

  • Проверка на ошибку sql запроса
  • Проверка на ошибки установить в браузере
  • Проверка на ошибки система raw
  • Проверка на ошибки с реализацией пагинации
  • Проверка на ошибки с объяснением

  • Понравилась статья? Поделить с друзьями:
    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии