60

60


С++ добавил к идее CLU дополнительную возможность: спецификации ис¬ключений, то есть включение в сигнатуру метода информации об исключениях, возникающих при вызове. В действительности спецификация исключения не¬сет двойной смысл. Она означает: «Я возбуждаю это исключение в коде, а вы его обрабатываете». Но она также может означать: «Я игнорирую исключение, которое может возникнуть в моем коде; обеспечьте его обработку». При осве¬щении механизмов исключений мы концентрировались на «обеспечении обра¬ботки», но здесь мне хотелось бы поближе рассмотреть тот факт, что зачастую исключения игнорируются, и именно этот факт может быть отражен в специ¬фикации исключения.
В С++ спецификация исключения не входит в информацию о типе функ¬ции. Единственная проверка, осуществляемая во время компиляции, относится к согласованному использованию исключений: к примеру, если функция или метод возбуждает исключения, то перегруженная или переопределенная версия должна возбуждать те же самые исключения. Однако, в отличие от Java, компи¬лятор не проверяет, действительно ли функция или метод возбуждают данное исключение, или полноту спецификации (то есть описывает ли она все исклю¬чения, возможные для этого метода). Если возбуждается исключение, не входя¬щее в спецификацию, программа на С++ вызывает функцию unexpected() из стандартной библиотеки.
Интересно отметить, что из-за использования шаблонов (templates) специ¬фикации исключений отсутствуют в стандартной библиотеке С++. В Java суще¬ствуют ограничения на использование параметризованных типов со специфи¬кациями исключений.
Перспективы
Во-первых, язык Java, по сути, стал первопроходцем в использовании контро¬лируемых исключений (несомненно из-за спецификаций исключений С++ и того факта, что программисты на С++ не уделяли им слишком много внима¬ния). Это был эксперимент, повторить который с тех пор пока не решился еще ни один язык.
Во-вторых, контролируемые исключения однозначно хороши при рассмот¬рении вводных примеров и в небольших программах. Оказывается, что трудно¬уловимые проблемы начинают проявляться при разрастании программы. Ко¬нечно, программы не разрастаются тут же и сразу, но они имеют тенденцию расти незаметно. И когда языки, не предназначенные для больших проектов, используются для небольших, но растущих проектов, мы в некоторый момент с удивлением обнаруживаем, что ситуация изменилась с управляемой на за¬труднительную в управлении. Именно это, как я полагаю, может произойти, ко¬гда проверок типов слишком много, и особенно в отношении контролируемых исключений.
Одним из важных достижений Java стала унификация модели передачи ин¬формации об ошибках, так как обо всех ошибках сообщается посредством ис¬ключений. В С++ этого не было, из-за обратной совместимости с С и возмож¬ности задействовать старую модель простого игнорирования ошибок. Когда Java изменил модель С++ так, что сообщать об ошибках стало возможно только посредством исключений, необходимость в дополнительных мерах принуждения в виде контролируемых исключений сократилась.
В прошлом я твердо считал, что для разработки надежных программ необхо¬димы и контролируемые исключения, и строгая статическая проверка типов. Однако опыт, полученный лично и со стороны1, с языками, более динамичны¬ми, чем статичными, привел меня к мысли, что на самом деле главные преиму¬щества обусловлены следующими аспектами:
1. Унификация модели сообщения об ошибках посредством исключений (независимо от того, заставляет ли компилятор программиста их обраба¬тывать).
2. Проверка типов, не привязанная к тому, когда она проводится — на ста¬дии компиляции или во время работы программы.
Вдобавок снижение ограничений времени компиляции весьма положитель¬но отражается на продуктивности программиста. С другой стороны, для ком¬пенсации чрезмерной жесткости статической проверки типов необходимы реф¬лексия и параметризация, как вы убедитесь в некоторых примерах книги.
Некоторые уверяли меня, что все сказанное является кощунством, безна¬дежно испортит мою репутацию, приведет к гибели цивилизации и провалу большой доли программных проектов. Вера в то, что выявление ошибок на ста¬дии компиляции спасет ваш проект, весьма сильна, но гораздо важнее созна¬вать ограничения того, на что способен компьютер. Стоит помнить:
«Хороший язык программирования помогает программистам писать хоро¬шие программы. Ни один из языков программирования не может запретить сво¬им пользователям писать плохие программы ».
В любом случае исчезновение когда-либо из Java контролируемых исключе¬ний весьма маловероятно. Это слишком радикальное изменение языка, и защит¬ники их в Sun весьма сильны. История Sun неотделима от политики абсолютной обратной совместимости — фактически любое программное обеспечение Sun ра¬ботает на любом оборудовании Sun, как бы старо оно ни было. Но, если вы чув¬ствуете, что контролируемые исключения становятся для вас препятствием (особенно если вас заставляют обрабатывать исключение, а вы не знаете, как с ним поступить), существует несколько вариантов.
Передача исключений на консоль
В несложных программах, как во многих примерах данной книги, простейшим решением является передача исключения за пределы метода main(), на консоль. К примеру, при открытии файла для чтения (подробности вы вскоре узнаете) необходимо открыть и закрыть поток FilelnputStream, который возбуждает ис¬ключения. В небольшой программе можно поступить следующим образом (по¬добный подход характерен для многих примеров книги):
//• excepti ons/Mai nExcepti on.java
import java io *;
public class MainException {
// Передаем все исключения на консоль, public static void main(String[] args) throws Exception { // Открываем файл: FilelnputStream file =
new FilelnputStreamC'MainException.java");
// Используем файл // Закрываем файл- file.closeO.
}
} ///:-
Заметьте, что main() — такой же метод, как и все прочие; он тоже может иметь спецификацию исключений, и здесь типом исключения является Excep¬tion, базовый класс всех контролируемых исключений. Передавая его на кон¬соль, вы освобождаетесь от необходимости написания предложений try-catch в теле метода main().
Преобразование контролируемых исключений в неконтролируемые
Рассмотренный выше подход хорош при написании метода main(), но в более общих ситуациях не слишком полезен. Подлинная проблема возникает при на¬писании тела самого обычного метода, когда при вызове другого метода вы чет¬ко сознаете: «Понятия не имею, что делать с исключением дальше, но „съедать" мне его не хочется, так же как и печатать банальное сообщение». Проблема ре¬шается при помощи цепочек исключений. Управляемое исключение просто «заворачивается» в класс RuntimeException примерно так:
try {
// .. делаем что-нибудь полезное } са!сИ(НеЗнаюЧтоДелатьСЭтимКонтролируемымИсключением е) { throw new RuntimeException(e);
}
Решение идеально подходит для тех случаев, когда вы хотите «подавить» контролируемое исключение: вы не «съедаете» его, вам не приходится описы¬вать его в своей спецификации исключений, и благодаря цепочке исключений вы не теряете информацию об исходном исключении.
Описанная методика позволяет игнорировать исключение и пустить его «всплывать» вверх по стеку вызова без необходимости писать блоки try-catch и (или) спецификации исключения. Впрочем, при этом вы все равно можете пе¬рехватить и обработать конкретное исключение, используя метод getCause(), как показано ниже:
// exceptions/TurnOffChecking.java // "Подавление" контролируемых исключений, import java io *;
import static net mindview.util.Print.*;
class WrapCheckedException {
void throwRuntimeException(int type) { try {
switch(type) {
case 0: throw new FileNotFoundExceptionO; case 1: throw new IOExceptionO; case 2- throw new RuntimeExceptionCTfle Я?"). default: return;
}
} catch(Exception e) {
// Превращаем в неконтролируемое: throw new RuntimeException(e);
}
}
}
class SomeOtherException extends Exception {}
public class TurnOffChecking {
public static void main(String[] args) {
WrapCheckedException wee = new WrapCheckedExceptionO: // Можно вызвать throwRuntimeExceptionO без блока try, // и позволить исключению RuntimeException покинуть метод- wee. throwRuntimeExceptionO); // Или перехватить исключение: for (int i =0; i <4; i++) try {
if(i < 3)
wee throwRuntimeException(i);
else
throw new SomeOtherExceptionO: } catch(SomeOtherException e) {
print("SomeOtherException. " + e); } catch(RuntimeException re) {
try { продолжение &
throw re.getCauseO, } catch(FileNotFoundException e) {
print("FileNotFoundException. " + e); } catch(IOException e) {
print("IOException- " + e); } catch(Throwable e) {
print("Throwable. " + e);
}
}
}
} /* Output-
Fi1eNotFoundException: java.iо.Fi1eNotFoundException
IOException: java.io.IOException
Throwable: java.lang.RuntimeException. Где Я?
SomeOtherExcepti on: SomeOtherExcepti on
*///:-
Метод WrapCheckedException.throwRuntimeException() содержит код, генери¬рующий различные типы исключений. Они перехватываются и «заворачивают¬ся» в объекты RuntimeException, становясь таким образом «причиной» этих ис¬ключений.
При взгляде на класс TurnOffChecking нетрудно заметить, что вызвать метод throwRuntimeException() можно и без блока try, поскольку он не возбуждает ни¬каких контролируемых исключений. Но когда вы будете готовы перехватить исключение, у вас будет возможность перехватить любое из них — достаточно поместить свой код в блок try. Начинаете вы с перехвата исключений, которые, как вы знаете, могут явно возникнуть в коде блока try, — в нашем случае первым делом перехватывается SomeOtherException. В конце вы перехватываете Runtime- Exception и заново возбуждаете исключение, являющееся его причиной (получая последнее методом getCause(), «завернутое» исключение). Так извлекаются из¬начальные исключения, обрабатываемые в своих предложениях catch.
Методика «заворачивания» управляемых исключений в объекты Runtime- Exception встречается в некоторых примерах книги. Другое возможное реше¬ние — создание собственного класса, производного от RuntimeException. Пере¬хватывать такое исключение не обязательно, но, если вы захотите, такая возможность существует.
Основные правила обработки исключений
Используйте исключения для того, чтобы:
• обработать ошибку на текущем уровне (избегайте перехватывать исклю¬чения, если вы не знаете, как с ними поступить);
• исправить проблему и снова вызвать метод, возбудивший исключение;
• предпринять все необходимые действия и продолжить выполнение без повторного вызова метода;
• попытаться найти альтернативный результат вместо того, который дол¬жен был бы произвести вызванный метод;
• сделать все возможное в текущем контексте и заново возбудить это же исключение, перенаправив его на более высокий уровень;
• сделать все, что можно в текущем контексте, и возбудить новое исключе¬ние, перенаправив его на более высокий уровень;
• завершить работу программы;
• упростить программу (если используемая вами схема обработки исклю¬чений делает все только сложнее, значит, она никуда не годится);

Report Page