Руководство по actionscript. часть 2, стр. 092
Следующий код демонстрирует метод, генерирующий ошибку, которая никогда не будет обработана:
public class ProblemClass { public function doSomething ( ):void { // Среда выполнения Flash: Хм. Блок try отсутствует. // Проверю-ка я, кто вызвал этот метод, throw new Error(«Something went wrong»);
}
}
public class ErrorDemo extends Sprite { public function ErrorDemo ( ) { // Среда выполнения Flash: Ага, вот кто вызвал метод doSomething( ). // Но здесь все равно нет блока try. Хм. Я просмотрела весь стек вызовов // до самого верха и не нашла блока try. Если это отладочная версия // среды выполнения Flash, я сообщу о проблеме. Возможно, программист
// знает, что нужно делать.
var problemObject-.ProblemClass = new ProblemClass( ); problemObject. doSomething( );
}
}
Как мы только что увидели, метод не обязан обрабатывать свои собственные исключения, поскольку исключения обладают способностью подниматься вверх по стеку вызовов. Исключения метода не обязан обрабатывать даже код, вызывающий этот метод. Обработка исключения допускается на любом уровне в стеке вызовов. Любой метод может делегировать обработку исключений коду, вызывающему данный метод. С другой стороны, генерация исключений, которые никогда не будут обработаны, является дурным тоном и оказывает опасное воздействие на программу. Вы должны всегда обрабатывать исключения или, если столкнетесь с необработанным исключением, в первую очередь изменить свой код, чтобы избежать повторной генерации данного исключения.
Блок finally
Пока мы рассмотрели лишь блоки try и catch инструкции try/catch/finally. Как уже известно, блок try содержит код, который может генерировать исключение, а блок catch включает в себя код, который выполняется в ответ на сгенерированное исключение. Для сравнения, блок finally содержит код, который выполняется всегда, независимо от того, возникло в блоке try исключение или нет.
Инструкция try/catch/finally содержит один (и только один) блок finally, который является ее последним блоком. Например:
try {
// Вложенные инструкции } catch (е:Тип0шибки1) {
II Обработка исключений типа Тип0шибки1. } catch (е:ТипОшибкип) {
II Обработка исключений типа ТипОшибкип. } finally {
// Этот код выполняется всегда, независимо от того, как завершается // выполнение блока try.
Руководство по actionscript. часть 2, стр. 093
}
Неправильное размещение блока finally вызовет ошибку на этапе компиляции. В предыдущем коде блок finally будет выполнен сразу после того, как:
? выполнение блока try завершится без ошибок;
? блок catch обработает исключение, сгенерированное блоком try;
? необработанное исключение поднимется вверх по иерархии объектов;
? оператор return, continue или break передаст управление программой за пределы блоков try или catch.
Блок final 1у инструкции try/ cat ch/final 1у обычно содержит очищающий код, который должен выполняться независимо от того, возникло исключение в соответ-
ствующем блоке try или нет. Предположим, что мы создаем игру в жанре «космический шутер» и определяем класс Spaceship, представляющий космические корабли. У класса Spaceship есть метод attackEnemy ( ), который выполняет следующее.
? Устанавливает текущую цель для космического корабля.
? Стреляет по выбранной цели.
? Удаляет выбранную цель (присваивая переменной currentTarget объекта Spaceship значение null).
Предположим, что в нашем гипотетическом приложении при выполнении первых двух из описанных задач может возникнуть исключение. Более того, предположим, что метод attackEnemy ( ) не обрабатывает эти исключения самостоятельно; он передает исключения вызывающему методу. Независимо от того, было сгенерировано исключение или нет, метод attackEnemy ( ) должен присвоить переменной currentTarget значение null.
Вот так выглядел бы метод attackEnemy ( ), если бы мы запрограммировали его с помощью оператора catch (то есть без использования блока finally):
public function attackEnemy (enemy:SpaceShip):void { try {
setCurrentTarget(enemy): fireOnCurrentTargetC ): } catch (e:Error) { // Удаляем текущую цель, если возникло исключение. setCurrentTarget(nul1): // Передаем исключение вызывающему методу, throw е:
}
// Удаляем текущую цель, если никаких исключений не возникло. setCurrentTarget(nul1);
}
Здесь мы вынуждены дублировать инструкцию setCurrentTarget (null). Мы поместили ее и внутрь блока catch, и после инструкции try/catch, гарантируя тем самым, что она будет выполнена независимо от того, возникло исключение в блоке try или нет. Тем не менее дублирование инструкции может привести к ошибке. В предыдущем методе программист мог бы легко забыть удалить текущую цель после блока try/catch.
Руководство по actionscript. часть 2, стр. 094
Если изменить нашу стратегию так, чтобы текущая цель удалялась в блоке fina 11 у, мы устраним ненужные команды в предыдущем коде:
public function attackEnemy (enemy:SpaceShiр):void { try {
setCurrentTarget(enemy): fireOnCurrentTarget( ): } finally { setCurrentTarget(nul1);
В модифицированной версии блок finally удаляет текущую цель независимо от того, возникло исключение или нет. Поскольку блок finally обрабатывает обе ситуации, у нас нет необходимости в блоке catch; мы можем просто позволить исключению автоматически подняться вверх к вызывающему методу.
Вы можете поинтересоваться, а зачем нам вообще нужен блок finally? Иными словами, почему нельзя просто использовать следующий код?
// Этот код выглядит подходящим, однако в нем существует проблема. // Сможете определить ее?
public function attackEnemy (enemy:SpaceShiр):void { setCurrentTarget(enemy); fireOnCurrentTarget( ); setCurrentTarget(nul1);
}
Запомните, что, когда генерируется исключение, управление программой передается в ближайший подходящий блок catch в стеке вызовов. Следовательно, если метод fir eOnCur rent Target ( ) генерирует исключение, управление передается в метод attackEnemy ( ), при этом обратно в метод fireOnCurrentTarget ( ) управление возвращено не будет и инструкция setCurrentTarget (null) останется невыполненной. Однако с помощью блока finally мы гарантируем, что инструкция setCurrentTarget (null) будет выполнена до того, как исключение поднимется вверх по иерархии объектов.
Пример метода attackEnemy ( ) отражает наиболее распространенное использование блока finally в многопоточных приложениях, в которых одновременно может выполняться сразу несколько фрагментов кода и которые разрабатываются с помощью таких языков программирования, как, например, Java. В языке Java следующая общая структура — обычное явление; она исключает возможность удаления объекта, выполняющего некую задачу, другим объектом в процессе выполнения текущей задачи:
// Установить состояние, обозначающее выполнение данным объектом текущей // задачи. Внешние объекты должны проверять состояние данного объекта перед // тем, как обратиться к нему или выполнить над ним какие-либо действия. doingSomething = true: try {
// Выполняем задачу. doSomething( ); } finally {
// Сбросить состояние, обозначающее выполнение текущей задачи (независимо // от того, возникло в процессе выполнения задачи исключение или нет). doingSomething = false:
}
В языке ActionScript приведенный код, управляющий состоянием объекта, на самом деле необязателен, поскольку разрабатываемые с помощью этого языка приложения являются однопоточными, следовательно, никакой внешний объект не сможет изменить состояние другого объекта, выполняющего некий метод. Таким образом, в языке ActionScript блок finally используется гораздо реже, чем в языках, применяемых для разработки многопоточных приложений. Тем не менее этот блок
можно использовать для организационных целей — помещать в него код, который выполняет очистку после выполнения другого кода.
Руководство по actionscript. часть 2, стр. 095
Вложенные исключения
До сих пор мы использовали только одноуровневые инструкции try/catch/ finally, однако логика обработки исключений может быть и вложенной. Инструкция try/catch/finally может размещаться внутри блока try, catch или finally другой инструкции try/catch/finally. Такое иерархическое вложение позволяет любому блоку инструкции try/catch/finally выполнять код, который, в свою очередь, может генерировать исключения.
Предположим, что мы создаем многопользовательское веб-приложение, представляющее доску объявлений. Мы определяем следующие классы: BulletinBoard — основной класс приложения, GUIManager — класс, управляющий пользовательским интерфейсом, и User — класс, который представляет пользователя на доске. В классе BulletinBoard мы описываем метод populateUserList ( ), который отображает список активных пользователей на текущий момент. Выполнение мето-дapopulateUserList ( ) состоит из двух этапов: на первом этапе метод получает экземпляр класса List из экземпляра класса GUIManager нашего приложения. Класс List представляет отображаемый на экране список пользователей. Затем метод populateUserList ( ) заполняет экземпляр класса List пользователями из переданного массива экземпляров класса User. На обоих этапах существует потенциальная возможность возникновения исключения, поэтому в методе populateUserList ( ) используется вложенная структура try/catch/finally. Рассмотрим эту вложенную структуру поближе.
EcлинaпepвoмэтaпeвыпoлнeниямeтoдapopulateUserList ( ) экземпляр класса List окажется недоступным, будет сгенерировано исключение UserLis tNotFound экземпляром класса GUIManager. Исключение UserLi s tNotFound обрабатывается внешней инструкцией try/catch/finally.
Если, с другой стороны, экземпляр класса List окажется доступным, метод populateUserList ( ) перейдет к выполнению второго этапа, где с помощью цикла заполнит экземпляр класса List пользователями из переданного массива. На каждой итерации цикла, если ID текущего пользователя не может быть найдено, метод User. gelD ( ) генерирует исключение UserldNotSet. Оно обрабатывается вложенной инструкцией try/catch/finally.
Рассмотрим этот код:
public function populateUserList (users:Array):void { try {
// Приступаем к выполнению этапа 1… получаем экземпляр класса List. // Если метод getllserListC ) сгенерирует исключение, будет выполнен // внешний блок catch.
var ulist:List = getGUIManager( ).getUserList( ); // Приступаем к выполнению этапа 2… заполняем экземпляр класса List, for (var i-.Number = 0; i < users.length: i++) { try {
var thisUser:User = User(users[i]);
// Если метод getID( ) сгенерирует исключение, будет выполнен // вложенный блок catch. В противном случае пользователь будет // добавлен в экземпляр класса List вызовом метода addltem( ). ulist. addItem(thisUser. getName( ). thisUser. getID( )); } catch (e:UserIdNotSet) { trace(e. message);
continue; // Пропускаем этого пользователя.
}
}
} catch (e:UserListNotFound) { trace(e. message);
}
}
Теперь, когда мы рассмотрели конкретный пример вложенного исключения, познакомимся с общим процессом обработки вложенных исключений.
Руководство по actionscript. часть 2, стр. 096
Если исключение генерируется в блоке try, вложенном в другой блок try, и внутренний блок try содержит блок catch, способный обработать сгенерированное исключение, выполняется внутренний блок catch и программа продолжает свое выполнение сразу после внутренней инструкции try/catch/finally.
try { try {
// Здесь генерируется исключение, throw new ErrorCTest error»); } catch (e;Error) { // Здесь обрабатывается исключение. trace(e. message); // Выводит; Test error
}
// Здесь продолжается выполнение программы. } catch (e:Error) { // Обработка исключений, генерируемых внешним блоком try.
}
Если, с другой стороны, исключение возникло в блоке try, вложенном в другой блок try, однако внутренний блок try не содержит блока catch, способного обработать данное исключение, сгенерированное исключение будет передаваться вверх к внешней инструкции try/catch/finally (и при необходимости дальше по стеку вызовов) до тех пор, пока не будет найден подходящий блок catch или пока не будет достигнута верхняя точка стека вызовов. Если исключение будет обработано в некоторой точке стека вызовов, то выполнение программы продолжится сразу после инструкции try/catch/finally, обработавшей это исключение. Обратите внимание, что в следующем примере кода (и последующих примерах) гипотетический тип данных ошибки SomeSpecificEr ror является заполнителем, используемым для того, чтобы сгенерированное исключение не было поймано. Чтобы протестировать пример кода в вашем собственном коде, вы должны создать подкласс SomeSpecificError класса Error.
try { try {
// Здесь генерируется исключение, throw new Error(«Test error»); } catch (e;SomeSpecificError) { // Здесь исключение не обрабатывается. trace(e. message); // Инструкция никогда не будет выполнена, // поскольку типы не совпадают.
}
} catch (е:Error) { // Исключение обрабатывается здесь. trace(e. message); // Выводит; Test error
}
// Выполнение программы продолжается здесь, сразу после обработки исключения // внешним блоком catch.
Руководство по actionscript. часть 2, стр. 097
Если исключение генерируется в блоке try, вложенном в блок catch, и внутренний блок try не имеет блока catch, способного обработать данное исключение, поиск подходящего блока catch начинается за пределами внешней инструкции try/ catch/finally:
try {
// Здесь генерируется внешнее исключение, throw new Error(«Test error 1″); } catch (e;Error) { // Здесь обрабатывается внешнее исключение. trace(e. message); // Выводит: Test error 1 try {
// Здесь генерируется внутреннее исключение, throw new ErrorC’Test error 2″); } catch (e:SomeSpecificError) { // Внутреннее исключение здесь не обрабатывается. trace(e. message); // Инструкция никогда не будет выполнена, // поскольку типы не совпадают.
}
}
// Процесс поиска подходящего блока catch для внутреннего исключения // начинается здесь.
Наконец, если исключение генерируется в блоке try, вложенном в блок finally, но при этом предыдущее исключение уже находится в процессе подъема по стеку вызовов, новое исключение будет обработано до того, как предыдущее исключение продолжит свой подъем по иерархии объектов.
// Этот метод генерирует исключение в блоке finally, public function throwTwoExceptions ( ):void { try {
// Здесь генерируется внешнее исключение. Поскольку этот блок try // не имеет соответствующего блока catch, внешнее исключение начинает // свой подъем по иерархии объектов, throw new ErrorC’Test error 1″); } finally { try {
// Здесь возникает внутреннее исключение. Внутреннее исключение
// будет обработано до того, как внешнее исключение фактически начнет
// подъем по иерархии объектов, throw new ErrorC’Test error 2″); } catch (e:Error) { // Внутреннее исключение обрабатывается здесь, tracer Internal catch: » + e. message);
}
}
}
// Где-то в другом месте, внутри метода, // вызывающего предыдущий метод, try {
throwTwoExceptions( ); } catch (e:Error) { // Здесь обрабатывается внешнее исключение, // поднявшееся из метода throwTwoExceptions( ). trace(«External catch: » + e. message);
}
// Вывод (обратите внимание, что внутреннее исключение обработано первым): // Internal catch: Test error 2 // External catch: Test error 1
Если бы в предыдущем коде исключение, сгенерированное в блоке finally, не было обработано, среда Flash сообщила бы об ошибке на этапе отладки и прекратила бы выполнение оставшегося кода в стеке вызовов. В результате никакой код в стеке вызовов не обработал бы сгенерированное первым внешнее исключение. Следующий код демонстрирует описанный сценарий. Этот код генерирует необрабатываемое исключение в блоке finally. В итоге исключение, сгенерированное внешним блоком try, будет потеряно.
Руководство по actionscript. часть 2, стр. 098
Try {
// Здесь генерируется внешнее исключение, throw new ErrorC’Test error 1″); } finally { try {
// Здесь генерируется внутреннее исключение, throw new ErrorC’Test error 2″); } catch (e:SomeSpecificError) { // Внутреннее исключение здесь не обрабатывается. trace(«internal catch: » + e. message); // Инструкция никогда не будет
// выполнена, поскольку типы
// не совпадают.
}
}
// Процесс поиска подходящего блока catch для внутреннего исключения // начинается здесь. Если внутреннее исключение не будет обработано, о нем // сообщит среда выполнения Flash на этапе отладки, при этом передача // внешнего исключения вверх по иерархии объектов будет прекращена.
Предыдущий код демонстрирует влияние необработанного исключения на ход выполнения программы в одном сценарии, однако хочется еще раз повторить, что
не следует допускать появления необрабатываемых исключений. В предыдущем случае мы должны либо обработать исключение, либо модифицировать наш код таким образом, чтобы данное исключение больше не появлялось.
Изменение хода выполнения программы в инструкции try/catch/finally
На протяжении этой главы мы неоднократно убеждались в том, что инструкция throw изменяет ход выполнения программы. Когда среда Flash встречает инструкцию throw, она немедленно прекращает выполнение текущей задачи и передает управление программой подходящим блокам catch и finally. Тем не менее блоки catch и finally также могут влиять на ход выполнения программы с помощью оператора return (в случае метода или функции), break или continue (в случае цикла). Более того, операторы return, break и continue могут также использоваться в блоке try.
Чтобы познакомиться с правилами изменения хода выполнения программы в инструкции try/catch/finally, рассмотрим, как оператор return влияет на ход выполнения программы в блоках try, catch и finally. Следующий пример кода содержит функцию changeFlow ( ), которая демонстрирует ход выполнения программы в различных гипотетических ситуациях.
Руководство по actionscript. часть 2, стр. 099
В листинге 13.1 показан оператор return в блоке try, помещенный перед инструкцией, генерирующей ошибку. В данном случае метод выполняется нормально и никакая ошибка не будет сгенерирована или обработана. Однако перед возвратом из метода будет выполнен блок finally. Стоит отметить, что вы вряд ли увидите код, в точности повторяющий код из листинга 13.1, в реальной программе. В большинстве случаев оператор return используется в условных операторах и выполняется в ответ на некоторое определенное условие в программе.
Листинг 13.1. Использование оператора return в блоке try перед оператором throw
public function changeFlow ( ):void { try { return;
throw new ErrorC’Test error.»);
} catch (e:Error) {
trace(«Caught: » +- e. message); } finally {
trace(«Finally executed.»);
}
traceC’Last line of method.»);
}
// Вывод после вызова метода changeFlow( ): // Finally executed.
В листинге 13.2 показан оператор return в блоке try, помещенный после инструкции, генерирующей ошибку. В данном случае return не выполнится, поскольку ошибка будет сгенерирована до того, как программа дойдет до этого оператора. Как только
ошибка будет обработана и выполнение инструкции try/ cat ch/finally завершится, выполнение программы продолжится после данной инструкции try/catch/finally и выход из метода произойдет в конце его тела. Снова повторим, что листинг 13.2 всего лишь демонстрирует принцип, но не является типичным в реальном сценарии, поскольку ошибка обычно генерируется на основании некоторого условия.
Руководство по actionscript. часть 2, стр. 100
Листинг 13.2. Использование оператора return в блоке try после оператора throw
public function changeFlow ( ):void { try {
throw new ErrorC’Test error.»); return;
} catch (e:Error) {
trace(«Caught: » + e. message); } finally {
trace(«Finally executed.»);
}
traceC’Last line of method.»);
}
// Вывод после вызова метода changeFlow( ): // Caught: Test error. // Finally executed. // Last line of method.
Листинг 13.3 демонстрирует использование оператора return в блоке catch. В данном случае return будет выполнен после завершения процесса обработки ошибки. При этом код, размещенный после инструкции try/catch/finally, выполнен не будет. Однако, как обычно, перед возвратом из метода будет выполнен блок fin а 11 у. В отличие от листингов 13.1 и 13.2 данный код является типичным в реальном сценарии, когда выполнение метода прекращается при возникновении ошибки.
Листинг 13.3. Использование оператора return в блоке catch
public function changeFlow ( ):void { try {
throw new ErrorC’Test error.»); } catch (e:Error) {
trace(«Caught: » + e. message);
return; } finally {
traceC’Finally executed.»);
}
traceC’Last line of function.»);
}
// Вывод после вызова метода changeFlow( ): // Caught: Test error. // Finally executed. ,
Из-за известной ошибки в приложении Flash Player 9 при выполнении кода из листингов 13.2 и 13.3 происходит обращение к несуществующей области стека. Корпорация Adobe планирует исправить эту проблему в следующей версии приложения Flash Player.
Руководство по actionscript. часть 2, стр. 101
В листинге 13.4 показан оператор return в блоке finally. В данном случае он выполняется тогда, когда выполняется блок finally (как уже известно, выполнение блока finally происходит после завершения соответствующего блока try одним из следующих способов: без ошибок; с обработанной ошибкой; с необработанной ошибкой; в результате вызова операторов return, break или continue). Обратите внимание, что оператор return в листинге 13.4 предотвращает выполнение кода, размещенного после инструкции try/catch/ finally. Вы можете использовать подобную методику для завершения метода после выполнения блока кода независимо от того, было сгенерировано исключение или нет. При этом вся инструкция try/catch/finally обычно помещается внутрь условного оператора (иначе оставшаяся часть метода никогда не будет выполнена!).
Листинг 13.4. Использование оператора return в блоке finally
public function changeFlow ( ):void { try {
throw new ErrorC’Test error.»); } catch (e:Error) {
trace(«Caught: » + e. message); } finally {
trace(«Final! у executed.»);
return;
}
traceC’Last line of method.»); // He выполняется.
}
// Вывод после вызова метода changeFlow( ): Caught: Test error. Finally executed.
^ I Если оператор return в блоке finally вызывается после того, как был вызван оператор м$ 4 * return в соответствующем блоке try, вызов return в блоке finally отменяет предыдущий цу вызов return.
Обработка предопределенного исключения
В самом начале этой главы мы узнали, что с помощью инструкции try/catch/ finally можно обрабатывать как предопределенные, так и пользовательские ошибки. До настоящего момента все обрабатываемые нами ошибки были пользовательскими. Сейчас мы рассмотрим инструкцию try/catch/finally, обрабатывающую предопределенную ошибку.