Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
108
Добавлен:
02.05.2014
Размер:
117.25 Кб
Скачать
  1. Определение.

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

В Delphi этот класс называется TException.

В C# - Exception.

В Java - Throwable

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

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

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

  1. Возникновение.

В Delphi есть оператор

raise exp;

в C# и Java -

throw exp;

exp – некоторое выражение типа, который относится к базовому типу, или является производным от него.

  1. Распространение.

В этих языках нет механизма свертки стека. Это понятие специально выделяется в языке С++ потому, что в С++ есть семантика автоматического вызова деструктора. Во всех этих трех языках семантики автоматического вызова деструктора нет. В Java вообще нет понятия деструктора.

MIL – Microsoft Intermediate Language – Промежуточный Язык Microsoft. Microsoft всячески призывает изучать промежуточный язык. Если вы изучите MIL, то вам станет ясно, что деструктор языка C# сводится к тому, что компилятор подставляет вызов метода finalize, а во всех руководствах к C# стыдливо говориться, что деструктор, в этом языке только называется деструктором, но на самом деле им не является.

Т.е. реально ни в C#, ни в Java деструкторов нет. В Delphi деструктор есть, но автоматической семантики вызова этого деструктора нет. Вот почему в этих языках нет понятия свертки стека. Т.е. свертка ближе к языку Ада, но она существенно легче реализуется.

С++ очень удобная семантика работы с ресурсами (захват ресурса в локальном объекте через конструктор, освобождение – через деструктор). В этих языках – по-другому. Во всех этих трех языках присутствует конструкция, которая отсутствует в языке С++:

в C# и Java

try{

блок

} finally {…}

После finally стоит не обработчик исключительных ситуаций, а код, который выполняется всегда вне зависимости от того, каким образом завершился блок – нормально или аварийно. Блок, стоящий после catch(…), будет выполняться, только если возникло исключение.

Синтаксис Delphi практически такойже

try

операторы

finally

end;

Операторы, стоящие после finally, выполняются всегда, не зависимо от того, было возбуждено исключение или нет. Этот оператор появился потому, что он моделирует семантику свертки стека. Работа с ресурсами в этих языках

захват ресурса

try

операторы

finally

освобождение ресурса

end;

Если ресурс – динамическая память, то в языках C# и Java писать его освобождение не нужно – она освободится. Но динамическая память – не единственный ресурс. Внешние устройства: принтеры, COM-порты и многое другое - внешние ресурсы, которые не освобождаются автоматически. Их необходимо освобождать вручную. Поэтому во всех трех языках присутствует такая конструкция. При захвате ресурса надо писать блок try – finally потому, что в процессе распространения исключений никаких сверток стека нет.

Тем не менее, принцип распространения исключений во всех этих языках одинаков, а именно – принцип завершения.

  1. Обработка исключений.

Синтаксис Java, C# очень схож с синтаксисом С++:

try{

блок

}catch(E1 e){…}

catch(E2 e){…}

В этих языках нет catch(…) потому, что любое исключение принадлежит соответствующему типу – DException, Exception или Throwable. Поэтому здесь такая конструкция не нужна. Вместо того, что писать catch(…) пишут, например, на языке Java

catch(Throwable e){…}

Это аналог catch(…). В С++ если все исключения являются потомками одного предка, catch(…) писать не надо. В данном случае мы можем из е получить хоть какую-то информацию. Такой catch должен стоять последним, поскольку все типы так или иначе выведены из Throwable.

В Delphi несколько другой синтаксис, но смысл тот же

try

операторы

except

on [имя:]тип do опер;

end;

Оператор после do может быть составным. В том случае, если нам не надо получать информацию из объекта, имя может отсутствовать.

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

Исключение считается обработанным, если нормальным образом завершился блок обработки исключения.

Как видно семантика практически одна и та же. Но есть, конечно, свои специфические особенности. Рассмотрим их.

В языке Java есть понятие спецификации исключительных ситуаций. Это в некоторой степени роднит его с С++. В С++ эта спецификация необязательна. В языке Java спецификация исключительных ситуаций является обязательной.

void f( ){

throw new Err( );

}

В этом месте компилятор выдаст ошибку потому, что в языке Java спецификация исключительных ситуаций является обязательной. По этому коду компилятор видит, что исключительная ситуация throw new Err( ) является необработанной потому, что она находится вне try-блока.

Err должен быть классом, выведенным из класса Throwable, иначе компилятор выдаст ошибку.

Throw может встречаться внутри try-блока, тогда если это исключение можно перехватить внутри него, то все в порядке, если – нет, то он помечает, что этот блок может выбрасывать исключения типа Err. Если оно находится вне блока, то f может выбрасывать ситуацию типа Err. Значит, она должна быть специфицирована. Спецификация исключительных ситуаций в Java имеет вид

void f( ) throws(Err) {

throw new Err( );

}

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

Раз спецификация исключительных ситуаций является обязательной, то статически можно проследить, какие исключения могут быть выданы на протяжении всей программы. Требование обязательной обработки исключительных ситуаций приводит к тому, что программиста заставляют писать эти исключительные ситуации. Как следствие, писать обработку ошибок и отказоустойчивые программы вообще на языке Java, оказывается значительно приятней, чем на других языках. В хорошей библиотеке на С++, в документации везде стоят соответствующие спецификации. Но они стоят в документации, и их можно просто проигнорировать, и компилятор не выдаст ошибки. В данном случае если проигнорировать спецификацию, компилятор выдаст ошибку. Поскольку все объекты, за исключением простых переменных, заводятся в динамической памяти, то практически любая функция на языке Java будет порождать новые объекты и может выбросить везде, где встречается new, исключение типа MemoryException. В результате получается, что мы должны писать throws практически в спецификации исключений каждой функции. К числу ошибок в языке Java относится ошибка виртуальной Java-машины. Для программиста на языке Java компьютера не существует, для него существует только виртуальная Java-машина. Т.е. фатальный сбой виртуальной Java-машины говорит о том, что не работает компьютер. Программным образом эту ошибку никак не исправить. Когда встречается такая ошибка надо сразу рапортовать разработчику виртуальной Java-машины, поскольку в процессе функционирования любой, даже ошибочной программы такие ошибки выдаваться не должны. Но по принципу Мерфи, если они могут появиться, они появятся. Причем они могут появиться в любом месте.

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

Базовый тип Throwable

Error Exception

"UserExeption" RuntimeExeption

MemoryException

"UsepException" – пользовательские исключения.

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

Классы, которые выведены из Error – это классы типа ошибка виртуальной Java-машины… Они нужны для сигнализации неисправимых ошибок. К сожалению не все ошибки можно исправить в программе. Если загорелась проводка, работать дальше программе не имеет смысла. В Java есть исключения, на которые мы обязаны реагировать, а есть исключения, на которые мы не обязаны реагировать. На исключения типа Error и на все исключения, которые выведены из RuntimeException мы не обязаны реагировать. И как следствие мы не обязаны их указывать в списке спецификаций исключений. Но все "UserException" мы указывать обязаны.

В C#, Delphi нет такого очень удобного механизма как в языке Java. Java изначально разрабатывалась как многоплатформенная среда, работающая на базе одного языка. Т.е. предполагается, что при работе на Java используется только Java. В рамках одного языка выразить подобного рода механизм не сложно. C# - это язык, который включен в систему .NET, базовой для которой является система базисных классов NET.Framework. Идея NET.Framework в том, что имеется базисная система типов и базисный набор компонент, который не зависит от языка. Все языки, входящие в систему .NET (сейчас это порядка 10 языков, в том числе C#), работают с единой библиотекой. Принципы обработки исключений в этих языках немного различаются. Например, в языке Basic принципы обработки исключений отличны от принципов обработки исключений, которые мы сейчас разбирали. Поэтому вводить какую-то единообразную технику в этих языках не представляется возможным.

Механизмы, отличные от тех, которые мы рассмотрели.

Главная идея обработки исключений, в языках, которые мы рассмотрели – динамическая ловушка (семантика завершения). Кроме семантики завершения есть, как назвал это Кауфман, ремонт на месте (семантика возобновления). В Visual Basic есть семантика возобновления.

on error вызов процедуры или оператор перехода

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

raise;

  • перевозбудить ошибку, пусть она идет на более высокий уровень.

resume;

  • возобновить оператор, на котором произошла ошибка.

resume next;

  • пропустить оператор, на котором произошла ошибка, и выполнять следующий.

pause;

Семантика resume и есть семантика возобновления. В такой семантике возможна ситуация, что ошибочный оператор, блок, который вызвал ошибку, будет перезапущен – то, что отсутствовало в семантике завершения. raise говорит о том, что блок не может обработать ошибку и поэтому она распространяется на более высокий уровень. raise позволяет реализовывать семантику завершения. Получается, что семантика возобновления является более мощной. Т.е. она может моделировать семантику завершения и в то же время в ней есть дополнительные полезные свойства. Когда в 90-м году обсуждалась модель исключений, которую собирались включить в предполагаемый стандарт языка С++, было 2 точки зрения:

  1. семантика завершения, как она была предложена Страуструпом на базе языка Ада

  2. семантика восстановления, на которой настаивали представители Microsoft.

В DOS, если забыли вставить дискету, система говорит

Abort Retry Ignore

Abort – значит raise. Retry – resume. Ignore – resume next. Это чистая семантика возобновления. Оказывается, что в некоторых частных случаях семантика возобновления работает очень хорошо. Все современные серьезные языки программирования применяют семантику завершения, хотя это частный случай семантики возобновления, а семантика возобновления не особенно более накладна (во многих источниках указано, что реализация семантики возобновления немножко сложнее, но накладные расходы примерно те же самые). Оказалось, что это тот самый случай, когда употребление более общей конструкции приводит к плохому коду. Одним из главных аргументов, после которого комитет по стандартизации языка С++ склонился к семантике завершения, было не то, что на той семантике настаивал Страуструп, а то, что реальные специалисты, которые писали действительно отказоустойчивые системы пришли к выводу, что с точки зрения обеспечения структурности и надежности механизма отказоустойчивости лучше использовать семантику завершения, т.е. более частную конструкцию. Решающим стало выступление разработчиков, которые разрабатывали отказоустойчивые системы на базе Cedar/Mesa из знаменитого исследовательского центра Xerox PAC фирмы Xerox Palau Alta. Они начинали свою работу, будучи сторонниками семантики обработки исключительных ситуаций по возобновлению. Общий проект занял порядка 10 миллионов строк кода. Потом они были вынуждены перейти к семантике завершения. И выяснилось, что из 10 миллионов строк кода только в одном случае действительно использовалась семантика возобновления. И там, можно было обойтись без нее. Люди, которые склонялись к одной семантике, и в то же время пытались писать отказоустойчивую программу, перешли на семантику завершения потому, что, на самом деле, ситуации, когда можно сделать ремонт на месте очень редки, и их можно свести к семантике завершения. Реально исправить ошибку на месте нельзя. Семантика возобновления опасна тем, что она создает иллюзию, что можно отремонтироваться на месте.

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

for(;;){

try{

… - тут возникает нехватка ресурсов

}

catch(…){… - попытка найти этот ресурс}

}

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

Соседние файлы в папке Лекции по программированию на ЯВУ