April
2007
Исключения в Java (exceptions, errors, исключительные ситуации)
Posted in: Java technologies, J2SE |
Эта статья посвящается очень важному вопросу программирования - исключительным ситуациям и ошибкам (exceptions and errors).
В языке Java исключения (Exceptions) и ошибки (Errors) являются объектами. Когда метод вызывает (бросает - throws) исключительную ситуацию, он на самом деле работает с объектом. Но такое происходит не с любыми объектами, а только с теми, которые наследуются от Throwable.
Упрощенную диаграмму классов ошибок и исключительний вы можете увидеть на следующем рисунке:
RuntimeException, Error и их наследников еще называют unchecked exception, а всех остальных наследников класса Exception - checked exception. Checked Exception обязывает пользователя обработать ее (использую конструкцию try-catch) или же отдать на откуп обрамляющим методам, в таком случае к декларации метода, который бросает проверяемое (checked) исключение, дописывают конструкцию throws, например:
public Date parse(String source) throws ParseException { ... }
К unchecked исключениям относятся, например, NullPointerException, ArrayIndexOutOfBoundsException, ClassCastExcpetion и так далее. Это те о�?ибки, которые могут возникнут практически в любом методе. Несомненно, описывать каждый метод как тот, который бросает все эти исключения, было бы глупо.
1. Так когда же нужно бросать о�?ибки?. На этот вопрос можно ответить просто: если в методе возможна ситуация, которую метод не в состоянии обработать самостоятельно, он должен “бросать” ошибку. Но ни в коем случае нельзя использовать исключительные ситуации для управления ходом выполнения программы.
Чаще всего Exceptions бросаются при нарушении контракта метода. Контракт (contract) - это негласное соглашение между создателем метода (метод сделает и/или вернет именно то, что надо) и пользователем метода (на вход метода будут передаваться значения из множества допустимых).
Нарушение контракта со стороны создателя метода - это, например, что-нибудь на подобии MethodNotImplementedYetException :).
Пользователь метода может нарушить контракт, например, таким способом: на вход Integer.parseInt(String) подать строку с буквами и по заслугам получить NumberFormatException.
Часто при реализации веб-сервисов первыми строками методов я пи�?у конструкции вида:
public Contract getContractById(String id) {
if (id == null) throw new NullPointerException("id is null");
...
}
Это помогает на вызывающей стороне понять, что они нару�?ают контракт метода, причиной чего часто может быть о�?ибка в логике их же приложения.
2. А что собственно бросать?. Выбор не то чтобы сильно велик, но и не однозначен: checked, unchecked (runtime), unchecked (error).
Сразу скажу, в подавляющем боль�?инстве случаев Error вам не понадобится. Это в основном критические о�?ибки (например, StackOverflowError), с которыми пусть работает JVM.
Checked Exceptions, как было написано вы�?е, заставляет программиста-пользователя написать код для ее обработки или же описать метод как “вызывающий исключительную ситуацию”.
С unchecked exception можно поступить по-разному. В случае с такими о�?ибками, пользователь сам ре�?ает, будет он обрабатывать эту о�?ибку, или же нет (компилятор не заставляет это делать).
Можно написать следующее простое правило: если некоторый набор входящих в метод данных может привести к нару�?ению контракта, и вы считаете, что программисту-пользователю важно разобраться с этим (и что он сможет это сделать), описывайте метод с конструкцией throws, иначе бросайте unchecked exception.
3. Ну и как это обрабатывать? Обрабатывать о�?ибку луч�?е там, где она возникла. Если в данном фрагменте кода нет возможности принять ре�?ение, что делать с исключением, его нужно бросать даль�?е, пока не найдется нужный обработчик, либо поток выполнения программы не вылетит совсем.
Вы наверняка знаете, что обработка исключений происходит с помощью блока try-catch-finally. Сразу скажу вам такую вещь: никогда не используйте пустой catch блок вида try { … } catch(Exception e) { }!
Если Вы уверены, что исключения в блоке try не возникнет никогда, напи�?ите комментарий, как например в этом фрагменте кода:
StringReader reader = new StringReader("qwerty");
try { reader.read(); }
catch (IOException e) { /* cannot happen */ }
Если же исключение в принципе может возникнуть, но только действительно в “исключительной ситуации” когда с ним ничего уже сделать будет нельзя, луч�?е оберните его в RuntimeException и пробросьте наверх, например:
String siteUrl = ...;
...
URL url;
try { url = new URL(siteUrl); }
catch (MalformedURLException e) {
throw new RuntimeException(e);
}
Скорее всего о�?ибка здесь может возникнуть только при неправильной конфигурации приложения, например, siteUrl читается из конфигурационного файла и кто-то допустил опечатку при указании адреса сайта. Без исправления конфига и перезапуска приложения тут ничего поделать нельзя, так что RuntimeException вполне оправдан.
4. Зачем нужно все это делать? А почему бы и нет :). Если серьезно - правильное использование Exceptions и корректная их обработка сделают код более понятным, гибким, структурированным и возможным для повторного использования.
Updated 28.08.2009
Хочу показать вам несколько интересных моментов, которые касаются исключений и блоков try-catch-finally.
Можно ли сделать так, чтобы блок finally не выполнился? Можно:
public class Test1 {
public static void main(String[] args) {
try {
throw new RuntimeException();
} catch (Exception e) {
System.exit(0);
} finally {
System.out.println("Please, let me print this.");
}
}
}
Ну и еще одна интересная вещь. Какое исключение будет выбро�?ено из метода:
public class Test {
public static void main(String[] args) {
try {
throw new NullPointerException();
} catch (NullPointerException e) {
throw e;
} finally {
throw new IllegalStateException();
}
}
}
Правильный ответ - IllegalStateException. Не смотря на то, что в блоке catch происходит повторный “выброс” NPE, после него выполняется блок finally, который перехватывает ход выполнения программы и бросает исключение IllegalStateException.
Жду ваших вопросов и комментариев.
11 Comments »
RSS feed for comments on this post.
Но ни в коем случае нельзя использовать исключительные ситуации для реализации частей бизнесс-логики.
Я бы сказал не стоит использовать исключения для управления flow алгоритма (типа выхода из вложенного цикла и все такое). А вот для реализации частей бизнес-логики как раз таки иногда и нужно, но только те которые checked. Классический пример - снятие денег со счета, когда выкидывается исключение, говорящее о том что деню?ок то не хватает. Вот тут как раз клиенту метода снятия надо реализовать бизнес-логику по обработке такой ситуации.
Ну а в тех случаях когда речь идет о нару?ении контракта метода (не бизнес-ограничений) не надо и задумываться - только unchecked, и ловить их должен код, находящийся в самом начале потока, и ничего кроме логирования информации об о?ибки с ними сделать он не может, да и не должен.
P.S. Ну конечно бывают ситуации исключительные. Но это не более 20%.
Классический пример - снятие денег со счета, когда выкидывается исключение, говорящее о том что деню?ок то не хватает.
Очень дурной стиль так делать.
Steve McConnell: “По оценкам некоторых ученых код на целых 90% состоит из блоков обработки исключительных ситуаций, о?ибок и т. п., из чего следует, что только 10% кода отвечают за номинальный режим работы программы (Shaw in Bentley, 1982).”
Очень дурной стиль так делать.
Хм. это почему? Альтернатива? Я же говорю не об использовании исключений для управление flow программы, а об использовании их для сигнализации того, что запро?енная операция не может быть выполнена с объяснением причины.
Я бы не сказал, что использовать исключительные ситуации для реализации логики - это плохо.
смотрите:
class Banka {
private double _bablo;
public double getBablo (){return _bablo;}
public void foo (int a) {
if (_bablo < a)
b.foo (_bablo);
else
_bablo -= a;
}
}
использование в вариенте 1.
Banka b = new Banka ();
double sum = 0;
double bablo = b.getBablo();
b.foo (100);
if (bablo <= 100)
sum += 100;
else
sum += bablo;
второй вариант:
class Banka {
private double _bablo;
public double getBablo (){return _bablo;}
public void foo (int a) {
if (_bablo < a)
throw new NoBablo();
_bablo -= a;
}
}
использование в вариенте 2.
Banka b = new Banka ();
try{
b.foo (100);
}
catch (NoBablo e){
// no bablo
}
Кажется, что разницы между первым подходом и вторым нет. ? там и там есть контроль над снятием денег. Но в первом случае мы размазываем логику работы банка во множестве мест.
А теперь представим ситуацию, что правило снятия бабла поменялось (хотя на стадии проектирования аналитик клялся, что никогда и нигде такого быть не может, но мы все знаем цену их обещаниям) и теперь можно снимать бабло на сумму боль?ую чем есть на счете. Соотвественно, правим метод (это легко), а потом по?ли искать в тысяче мест где возможно мы вставили проверку перед вызовом foo (снятием денег).
В предыдущем пример вкрался подлый баг, исправьте пожалуйста, там символ “мень?е чем” вставился как тег. ? “полетела” часть кода.
Ну и если совсем обнаглеть
А слабо сделать Preview перед отправкой сообщения.
2 lucker: ну да, я и имел в виду управление ходом выполнения программы. Грубый пример - поймать NPL, сделать заключение, что объект таки да null :), ну и как-то это подправить.
2 black zorro: Весь код пропал куда-то. ?справил на сколько смог понять идею. Если что-то напутал, напи?и, пожалуйста.
А на счет preview - я PHP плохо знаю. Разве что найду где-нибудь plugin с функциональностью предварительного просмотра.
Насчет плагинов для wordpress.
Сразу предупредив, что с wordpress знаком ?апочно (собственный сайт на mediawiki).
По следующей ссылке Вы можете найти каталог с плагинами рас?иряющими функции комментирования:
http://blog.trampampam.ru/wordpress/comments_plugins/
Чтобы не играть роль google ре?ил поставить парочку из них и посмотреть функциональность.
http://wordpress.org/extend/plugins/live-comment-preview/
http://blogwaffe.com/ajax-comment-preview/ - установился без проблем, внизу после формы добавления комментария появилась кнопка “Preview”. Хотя зачем нужна эта кнопка я не понял, т.к. при наборе текста сразу после text-области появляется предпросмотр. Впечатления положительные, т.к. попытка ввести текст с символами “мень?е чем” и “боль?е чем” была отловлена.
Посоветую еще поставить http://sw-guide.de/wordpress/plugins/edit-comments-xt/. Добавляется функция редактирования своего комментария в течении некоторого времени после отправки. ?з минусов: редактирование только для зарегистрированных пользователей.
Подсветка: в коментариях может встречаться пример исходников и их было бы тоже очень неплохо подсветить. Я пользуюсь библиотечкой от “softwaremaniac-а”,
но еще рекомендуют codecolorer (никогда не сталкивался, но учитывая что он построен на базе geshi все должно быть “колечком”).
http://www.tretyak.com/2007/05/08/syntax-highlighters-for-wordpress.html
?справленный пример кода - http://paste.bradleygill.com/index.php?paste_id=185
2 black zorro: можно написать метод foo таким образом чтоб он сам проводил проверку (”хватает ли на счете”), и возвращал boolean.
public boolean foo (int a) {
if (_bablo >= a){
bablo-=a;
return true;
}
return false;
}
————————————————————————–
lucker wrote:
Ну а в тех случаях когда речь идет о нару?ении контракта метода (не бизнес-ограничений) не надо и задумываться - только unchecked, и ловить их должен код, находящийся в самом начале потока, и ничего кроме логирования информации об о?ибки с ними сделать он не может, да и не должен.
————————————————————————–
такой подход приведет к тому что код в начале потока будет в 80% случаев получать исключение неизвестно по какой причине и как возник?ее, например стэк трэйс на вэб странице, ни один из модулей который будет использовать такой метод и понятия не будет иметь о том что гдето в глубине ктото выбросил анчекед исключение и результат его может быть самым удручающим,
например: если такой метод кто-то начнет использовать в цикле то из-за одноко неправильного объекта переданого в метод, который выкинет анчекед из-за нару?ения контракта, вместо скажем ожидаемого списка из нескольких строк на экране клиент не увидит ни одного, а мог бы просто увидеть на один мень?е (тот который послужил причиной исключения) А произойдет это потому что девелопер который будет писать вывод понятия не будет иметь что данный конкретный метод
может просто выкинуть тот или иной рантайм, если такой метод в случае нару?ения контракта выбросит чекед исключение то пользователь метода будет иметь информацию и ре?ит что ему делать с данным исключением, код будет иметь более предсказуемое поведение а следственно станет более професиональным.
?спользование анчекед исключений где попало как раз и делает код мало пригодным к использованию, поскольку как раз скрывает тот самый контракт метода, когда пользователь метода понятия не имеет какие исключения могут вылететь из данного метода. А вылетать такие исключения как раз любят на продак?ене во время демонстрации системы заказчику.
Статья кстати выглядит немного поверхностной, основная проблема в написании комерческого софта именно в том чтобы сделать его реюзабельным и легко изменяемым к постоянно меняющимся требованиям при этом надежным, предсказуемым и устойчивым. Поэтому приведенная цитата о том что 90% кода обрабатывает исключения а 10% делает работу - это цитата человека который врядли писал что-то что потом используют другие… поскольку работа програмы включает в себя и надежность и предсказуемость и повторяемость результатов и мас?табируемость и возможность использовать код еще кем-то, все то что отличает комерческий код профи от кода студента.
Hi! This can be my first comment right here and so i i would like to present a simple shout out and show you I really take pleasure in looking at your blog posts. Are you able to advise any blogs