Основы объектно-ориентированного программирования - Бертран Мейер
Шрифт:
Интервал:
Закладка:
meaning (except: INTEGER)
-- Сообщение, описывающее природу исключения с кодом except
is_assertion_violation: BOOLEAN
-- Является ли последнее исключение нарушением утверждения
-- или нарушением убывания варианта цикла
ensure
Result = (exception = Precondition) or (exception = Postcondition) or
(exception = Class_invariant) or
(exception = Loop_invariant) or (exception = Loop_variant)
is_system_exception: BOOLEAN
-- Является ли последнее исключение внешним событием
-- (ошибкой операционной системы)?
is_signal: BOOLEAN
-- Является ли последнее исключение сигналом операционной системы?
tag_name: STRING
-- Метка утверждения, нарушение которого привело к исключению
original_tag_name: STRING
-- Метка последнего нарушенного утверждения оригинальным исключением.
recipient_name: STRING
-- Имя программы, чье выполнение было прервано последним исключением
class_name: STRING
-- Имя класса, включающего получателя последнего исключения
original_recipient_name: STRING
-- Имя программы, чье выполнение было прервано
-- последним оригинальным исключением
original_class_name: STRING
-- Имя класса, включающего получателя последнего оригинального исключения
Имея эти свойства, предложение rescue может управлять каждым исключением особым способом. Например, в классе, наследуемом от EXCEPTIONS, предложение rescue можно написать так:
rescue
if is_assertion_violation then
"Случай, обрабатывающий нарушение утверждений"
else if is_signal then
"Случай, обрабатывающий сигналы операционной системы"
else
...
end
Используя класс EXCEPTIONS, можно модифицировать пример quasi_inverse, чтобы он выполнял retry только при переполнении. Другие исключения, например, нажатие пользователем клавиши "break" не должны приводить к retry. Инструкция в предложении rescue теперь может иметь вид:
if exception = Numerical_error then
division_tried := True; retry
end
Так как здесь нет else ветви, то исключения, отличные от Numerical_error, будут причиной отказа - корректное следствие, поскольку программа не имеет рецепта восстановления в подобных случаях. Иногда предложение rescue пишется специально для того, чтобы обработать определенный вид возможных исключений. Этот стиль позволяет избежать анализа других неожиданных видов исключений.
Какой должна быть степень контроля?
Могут возникнуть замечания по поводу уровня обработки специфических исключений, иллюстрируемых двумя последними примерами. В этой лекции проводилась та точка зрения, что исключение - нежелательное событие; когда оно возникает, то естественная реакция ПО и его разработчика - "я не хочу быть здесь! Выпустите меня отсюда, как можно скорее!". Это, кажется, несовместимым с проведением в предложении rescue глубокого анализа источника исключений.
По этой причине я пытался в моей собственной работе избегать детального разбора случаев причины исключений, стараясь показать, что обработка исключений лишь фиксирует ситуацию, если может, а затем либо fail, либо retry.
Этот стиль, вероятно, слишком строг, и некоторые разработчики предпочитают менее ограниченную схему, используя в полной мере механизм запросов класса EXCEPTIONS, позволяющий в тоже время оставаться дисциплинированным. Если вы хотите придерживаться такой схемы, то в классе EXCEPTIONS найдете все, что для этого нужно. Но всегда помните о следующем принципе:
Принцип Простоты Исключения
Вся обработка, выполняемая в предложении rescue, должна оставаться простой и фокусироваться на единственной цели - возвратить объект получателя в стабильное состояние, допуская повторение, если это возможно.
Исключения разработчика
Все исключения, изучаемые до сих пор, были результатом событий внешних по отношению к ПО (сигналы операционной системы) или принудительных следствий его работы (нарушение утверждений). В некоторых приложениях полезно, чтобы исключения возникали по воле разработчика в определенных ситуациях.
Такие исключения называются исключениями разработчика. Они характеризуются как целочисленным кодом, отличающимся от системных кодов, так и именем (строкой), которые могут быть использованы, например, в сообщениях об ошибке. Можно использовать следующие свойства для возбуждения исключения разработчика и для анализа его свойств в предложении rescue.
trigger (code: INTEGER; message: STRING)
-- Прерывает выполнение текущей программы, выбрасывая исключение с кодом
-- code и связанным текстовым сообщением.
developer_exception_code: INTEGER
-- Код последнего исключения разработчика
developer_exception_name: STRING
-- Имя, ассоциированное с последним исключением разработчика
is_developer_exception: BOOLEAN
-- Было ли последнее исключение исключением разработчика?
is_developer_exception_of_name (name: STRING): BOOLEAN
-- Имеет ли последнее исключение разработчика имя name?
ensure
Result := is_developer_exception and then
equal (name, developer_exception_name)
Иногда полезно связать с исключением разработчика контекст - произвольный объект, структура которого может быть полезной при обработке исключения разработчика:
set_developer_exception_context (c: ANY)
-- Определить c как контекст, связанный с последовательностью
-- исключений разработчика (причина вызова компонента trigger).
require
context_exists: c /= Void
developer_exception_context: ANY
-- Контекст, установленный последним вызовом
set_developer_exception_context
-- void, если нет такого вызова.
Эти свойства позволяют использовать стиль программирования, в котором обработка исключений представляет часть общего процесса работы с программными элементами. Авторы одного из трансляторов при разборе текстов предпочитали выбрасывать исключения при появлении особых случаев, после чего вызывать для их анализа специальные программы. Это не мой стиль работы, но по сути, ничего ошибочного в нем нет, так что механизм исключений разработчика для тех, кому нравится так работать.
Обсуждение
Мы закончили проектирование механизма исключений, совместимого с применяемым ОО-подходом, и следующего идеям Проектирования по Контракту. Благодаря инструкции retry, механизм получился более мощным, чем во многих языках программирования. В то же время он может казаться более строгим из-за акцента на сдержанность при определении точных причин исключения.
Давайте рассмотрим несколько альтернативных идей проектирования, которым можно было бы следовать, и обсудим, почему они были опущены.
Дисциплинированные исключения
Исключения, как они были введены, дают способ справиться с аномалиями, возникающими в процессе выполнения: нарушениями утверждений, сигналами аппаратуры, попытками получить доступ к void ссылкам.
Исследуемый нами подход основан на метафоре контракта, - ни при каких обстоятельствах программа не должна претендовать на успешность, когда фактически имеет место отказ в достижении цели. Программа может быть либо успешной (возможно, после исправления ситуации и нескольких попыток retry), либо приводить к отказу.
Исключения в языках Ada, CLU, PL/1 не следуют этой модели. В языке Ada ее инструкция
Raise exc
прервет выполнение программы и возвратит управление вызывающей программе, которая может обработать исключение в специальном обработчике, или вернет управление на уровень выше. Но здесь нет правила, ограничивающего действия обработчика. Следовательно, полностью возможно игнорировать исключение или вернуть альтернативный результат. Это объясняет, почему некоторые разработчики смотрят на механизм исключений просто как на средство обработки специальных случаев, не включенных в основной алгоритм. Такие приложения исключения рассматривают фактически raise как goto, что, очевидно, опасно, так как позволяет передавать управление за границы программы. По моему мнению, они злоупотребляют механизмом.
Традиционно есть две точки зрения на исключения. Первая признает исключения необходимым свойством. Она присуща большинству практикующих программистов, знающих как важно сохранить управление во время выполнения программы при возникновении ненормальных условий - аппаратных или программных ошибок. Вторая точка зрения присуща ученым, озабоченным корректностью и систематическим конструированием программ. Они с подозрением относятся к исключениям, рассматривая их как нечто нечистое, старающееся обойти стандартные правила управления программными структурами. Надеюсь, выше разработанный механизм способен примирить обе стороны.