Обработка исключений в языке Ада

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

Язык Ада предусматривает т.н. обработку исключений. Причём, бывают предопределённые исключения, предусмотренные создателями компилятора, а бывают исключения, созданные программистом.

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

Начнём с предопределённых исключений:

  • Constraint_Error — Ошибка ограничения.
  • Numeric_Error — Ошибка числа.
  • Program_Error — Ошибка программы.
  • Storage_Error — Ошибка памяти.
  • Tasking_Error — Ошибка задачи.
  • Data_Error - Ошибка данных.

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

My_Exception : exception;
...

Возбуждение (вызов) исключения также не представляет сложности:

raise My_Exception;
...

Рассмотрим пример:

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
 
procedure main is
    x, y, z : Integer;
begin
    Put("X = ");
    Get(x);
    Put("Y = ");
    Get(y);
 
    z := x / y;
    Put_Line(Integer'Image(x) & " / " & Integer'Image(y) & " = " & Integer'Image(z));
 
    exception
        when Data_Error => Put_Line("Цифирь давай, цифирь!");
        when Constraint_Error => Put_Line("Я такое не ем!");
        when others => Put_Line("Что-то пошло не так!");
end main;

Программа ожидает на входе два целых числа, а потом делит первое на второе.  Так вот, если вместо числа ввести букву или строку, то будет возбуждено исключение Data_Error. Если вместо второго числа ввести 0 то будет возбуждено исключение Constraint_Error. Ну а если на запрос ввода числа нажать, например, Ctrl-Z, то в данном случае будет отработана ветвь when others.

Если нам нужно на несколько исключений вывести одно и то же сообщение, то это можно сделать так:

exception
    when Data_Error | Constraint_Error => Put_Line("Что-то пошло не так!");
    ...

Допустим, что нам нужно при делении на 0 возбудить своё исключение, тогда его можно вызвать, например, так:

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
 
procedure main is
    x, y, z : Integer;
    Zero_Divide : exception; --Объявляем исключение
begin
    Put("X = ");
    Get(x);
    Put("Y = ");
    Get(y);
    if y = 0 then
        raise Zero_Divide; --возбуждаем своё исключение
    end if;
 
    z := x / y;
    Put_Line(Integer'Image(x) & " / " & Integer'Image(y) & " = " & Integer'Image(z));
 
    exception
        when Zero_Divide => Put_Line("Деление на НОЛЬ!"); --Если деление на 0
        when others => Put_Line("Что-то пошло не так!");
end main;

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

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
 
procedure main is
    x, y, z : Integer;
    Zero_Divide : exception;
begin
    loop
        declare
        begin
            Put("X = ");
            Get(x);
            Put("Y = ");
            Get(y);
            if y = 0 then
                raise Zero_Divide;
            end if;
 
            z := x / y;
            Put_Line(Integer'Image(x) & " / " & Integer'Image(y) & " = " & Integer'Image(z));
            exit; --Выход из цикла, если всё корректно
 
            exception
                when Zero_Divide => Put_Line("Деление на НОЛЬ!");
                when others => Put_Line("Что-то пошло не так!");
        end;
        Skip_Line;
    end loop;
    Put_Line("Продолжение выполнения программы");
end main;

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

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

Кроме того, если нужно, чтобы исключение обработала другая подпрограмма, то можно использовать raise без указания конкретного исключения:

exception
    when others => raise;

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

Также необходимо отметить, что в Аде есть возможность подавления исключений (подавления проверок). Для этого существует прагма (директива) компилятора Supress. Эта директива зависит от компилятора, т.е. её параметры у разных компиляторов различаются. Лично мне не доводилось использовать подавление исключений (пусть уж лучше будет лишняя проверка), но такая возможность есть.

pragma Suppress ( Range_Check ); --подавление проверки выхода за пределы допустимого диапазона значений.

Таким образом, если переменная может в соответствии с ограничениями типа иметь значения, например, от -10 до 10, а ей присваивается значение 11, то при использовании в программе (в разделе объявления переменных) pragma Suppress ( Range_Check ); исключение возбуждено не будет. К чему это приведёт - это другой вопрос, однако такая возможность есть и кому-то она может пригодится.