Опубликован: 23.10.2005 | Доступ: свободный | Студентов: 4086 / 201 | Оценка: 4.44 / 4.19 | Длительность: 33:04:00
Специальности: Программист
Лекция 15:

OO-программирование и язык Ada

Исключения

Пакет STACKS определяет два исключения в своем интерфейсе: Overflow и Underflow. Язык Ada допускает как собственные исключения с произвольными именами, так и предопределенные исключенияи, запускаемые оборудованием или операционной системой.

Некоторые элементы механизма обработки исключений языка Ada были введены в лекции 12 курса "Основы объектно-ориентированного программирования", так что здесь можно ограничиться коротким изучением исключений в подходе Ada к построению ПО.

Упрощение управляющей структуры

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

action1;
if error1 then
    error_handling1;
else
    action2;
    if error2 then
        error_handling2;
    else
        action3;
        if error3 then
            error_handling3;
        else
            ...

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

Возбуждение и обработка исключений

Чтобы возбудить исключительную ситуацию, а не обрабатывать ошибки на месте, можно переписать текст следующим образом:

action1;
if error1 then raise exc1; end;
action2;
if error2 then raise exc2; end;
action3;
if error3 then raise exc3; end;
...

При выполнении команды raise exc нормальный порядок вычислений прерывается, и управление передается обработчику исключений (exception handler), представленному специальным блоком подпрограммы и имеющему вид:

exception
    when exc1, ...=> treatment1;
    when exc2 ...=> treatment2;
    ...

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

Цепь вызовов (этот рисунок впервые появился в лекции 12 курса "Основы объектно-ориентированного программирования")

Рис. 15.1. Цепь вызовов (этот рисунок впервые появился в лекции 12 курса "Основы объектно-ориентированного программирования")

Говорят, что обработчик захватывает exc, если exc появляется в одном из его предложений when (или он содержит предложение вида when others ). Такой обработчик выполняет соответствующие команды (после символа => ), после чего управление передается вызывающей программе или заканчивается в случае главной программы. (Ada имеет понятие главной программы.) Если никакой обработчик в динамической цепи не обрабатывает exc, выполнение приложения заканчивается, и управление возвращается к операционной системе, а она, вероятно, выведет системное сообщение об ошибке.

Обсуждение

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

К техническим различиям можно отнести способы задания исключений. В одном случае используются множественные предложения when, в другом - наследование от класса EXCEPTIONS. Более важно включение в объектную нотацию возможности повторной попытки, что потребовало введения специального ключевого слова retry. Язык Ada не имеет подобной поддержки и требует для ее реализации использования goto или подобных управляющих структур.

Методологическое различие вытекает из принятой строгой политики, ведущей к принципу Дисциплинированной Обработки Исключений. Каждый обработчик исключений должен заканчиваться, кроме редких случаев "ложной тревоги", либо попыткой повторения, либо официальным отказом ("организованной паникой"). Язык Ada менее строг в этом отношении, что может приводить к некорректному использованию исключения, при котором вызывающая программа получит управление без устранения возникающих проблем.

Стоит повторить основное правило:

Правило исключений языка Ada

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

Исключения в Ada - это управляющие структуры, предназначенные для отделения обнаружения аварийных ситуаций от их обработки и сохранения простоты структуры ПО. Однако на практике этого часто не происходит.

Запись raise some_exception дает впечатление освобождения от запутанной и скучной задачи слежения за необычными ситуациями, позволяя сосредоточиться на самом алгоритме, имеющем дело с нормальной ситуацией. Но вызов исключения еще не решает задачи. Исключения в пакете STACKS типичны. Попытка поместить элемент в полный стек вызывает ошибку Overflow, а попытка доступа к пустому стеку вызывает Underflow. Как обрабатывать Underflow, ошибку, возникающую при вызове remove или item на пустом стеке? Обсуждение Проектирования по Контракту показало, что эти подпрограммы не могут знать, что следует делать в такой ситуации. Вся ответственность лежит на клиенте, вызвавшем эти подпрограммы, только он может решить, что следует делать. У него и должен содержаться код вида:

[2]
    use REAL_STACKS;
    procedure proc (...) is
        s: STACK; ...
    begin
        ... remove (s); ...
    exception
        when Underflow => action1;
        ...
    end proc;

Клиент должен точно определить, что происходит в случае ошибки. Опустить оператор when Underflow было бы ошибкой проекта. Сравните это с обычной, не основанной на исключении, формой вызова:

[3]
    if not s.empty then s.remove else action1 end

(или вариантом, определяющим ошибку апостериори). Форма [2], использующая исключения, отличается от формы [3] только двумя аспектами:

  • код для обработки ошибки action1 текстуально отделен от вызова, приведшего к ошибке;
  • обработка ошибки одинакова для всех подобных вызовов.

Хотя и желательно избегать глубоко вложенных структур обработки ошибок if... then... else..., приведенных в начале лекции, то место в алгоритме, где обнаруживается ошибка, часто предоставляет наилучшую информацию для ее обработки. Если разделить обнаружение и обработку, то могут потребоваться сложные управляющие структуры для случаев, требующих повторного запуска или продолжения обработки.

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

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

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