Содержание


Вселенная Java

Часть 1. Приемы для эффективной работы с исключительными ситуациями в Java

Comments

Серия контента:

Этот контент является частью # из серии # статей: Вселенная Java

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Вселенная Java

Следите за выходом новых статей этой серии.

Обработка исключительных ситуаций (англ. exceptions) занимает важное место в процессе разработки проектов на языке Java. Чтобы убедиться в этом, достаточно посмотреть, сколько исключительных ситуаций (или исключений) объявлено в основных пакетах Java и сторонних библиотеках. Также одним из первых аспектов, которые осваивают программисты при изучении языка Java, является использование конструкции try – catch – finally для перехвата и обработки исключительных ситуаций.

И хотя все программисты прекрасно владеют техническими аспектами обработки исключений при написании приложения, изначально мало кто задумывается о том, что делать с исключительными ситуациями после того, как приложение было переведено в режим эксплуатации. Чаще всего делается вывод, что «в ходе разработки все ошибки были устранены или предусмотрены и потому возникновение исключительных ситуаций исключено», или же этот аспект просто отдается на откуп инструментам – среде разработки и серверу приложений.

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

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

Поиск причины возникновения исключительной ситуации

Устранение ошибки, приведшей к возникновению исключения, всегда следует начинать с анализа трассировки стека методов, который автоматически выполняется виртуальной машиной Java при возникновении исключительной ситуации. В листинге 1 приведен исходный код класса ExceptionSample. В методе method1 этого класса выполняется попытка открыть поток ввода для несуществующего файла, в результате чего возникает стандартная исключительная ситуация – FileNotFoundException.

Листинг 1. Фрагмент кода с возникновением исключительной ситуации
1 public class ExceptionSample {
2     public static void main(String[] args) {
3         method1();
4     }
5
6     public static void method1() {
7         try {
8             File f1 = new File("file.txt");
9             FileInputStream fis1 = new FileInputStream(f1);
10         } catch (FileNotFoundException fnfe1) {
11             fnfe1.printStackTrace(System.err);
12         }
13     }
14 }

Если скомпилировать и запустить этот код, то в консоль Java будет распечатано содержимое стека методов на момент возникновения исключительной ситуации, как показано в листинге 2.

Листинг 2. Трассировка стека методов при возникновении исключительной ситуации
1 java.io.FileNotFoundException: file.txt (The system cannot find the file specified)
2         at java.io.FileInputStream.open(Native Method)
3         at java.io.FileInputStream.<init>(FileInputStream.java:106)
4         at mezon.universe.ExceptionSample.method1(ExceptionSample.java:9)
5         at mezon.universe.ExceptionSample.main(ExceptionSample.java:3)

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

Как видно, в строке 2 в листинге 2 выведен метод open класса FileInputStream, входящего в библиотеку классов JSE. Классы, входящие в эту библиотеку, являются ключевыми компонентами платформы Java, они проходят многоэтапное тестирование и ошибки в них практически исключены. Поэтому, хотя исключение и «вылетело» из этого метода, корень ошибки необходимо искать в другом месте. Метод init, указанный на строке 3, тоже принадлежит классу JSE и поэтому исключается.

Таким образом, после исключения системных методов JSE, под подозрение попадает метод method1, находящийся на строке 4. Этот метод относится к пользовательскому классу ExceptionSample, поэтому в нем вполне допускается наличие ошибок. И если посмотреть на строки 8 и 9 в листинге 1, то можно выявить причину возникновения исключительной ситуации: при попытке открытия файла не выполняется проверка того, что он действительно существует.

При анализе трассировки стека методов нужно обращать внимание не на первый метод, указанный в ней, а искать методы, в которых вероятнее всего могут находиться ошибки. В этом случае вполне применимо известное правило 80/20 – 80% ошибок находятся в 20% исходного кода. И эти 20% - это как раз специфический код приложения – методы, относящиеся к классам проекта, а основные 80% кода - это вызовы системных классов JSE, которым вполне можно доверять.

Анализ трассировок стека методов в JEE-проектах

В JEE-проекте при анализе трассировки необходимо учитывать несколько дополнительных моментов. Во-первых, из-за сложного жизненного цикла JEE-приложения в трассировке могут быть перечислены несколько десятков методов, а не четыре метода, как в листинге 2. Поэтому требуется особое внимание, чтобы найти среди всех этих методов те, которые относятся к классам приложения.

Во-вторых, кроме классов из библиотек JSE, в JEE-приложениях часто используются классы из сторонних библиотек (например, Spring или Hibernate). К сожалению, в сторонних библиотеках вполне могут находиться ошибки, так как небольшие команды, разрабатывающие эти библиотеки, не обладают достаточным количеством ресурсов, чтобы обеспечить исчерпывающее тестирование. Поэтому, если поиск ошибок в классах JEE-приложения не принес результатов, стоит поискать в Интернет информацию об ошибках, связанных с библиотечными классами, перечисленными в трассировке. Опытные программисты также могут загрузить исходный код библиотеки и попытаться найти ошибку самостоятельно.

Как правильно организовать регистрацию исключительных ситуаций

Как было показано, чтобы успешно локализовать и устранить причину возникновения исключительной ситуации, необходимо выполнить анализ трассировки стека методов в момент её возникновения. А для этого потребуется обеспечить регистрацию возникающих исключительных ситуаций, а при выполнении этого, в общем-то, простого действия, начинающие программисты очень часто допускают ошибки, которые затрудняют последующий анализ.

Самая «тяжелая» и трудноуловимая ошибка – это полное игнорирование исключительной ситуации. В этом случае первоначальная исключительная ситуация никак не фиксируется в журнальных файлах приложения и пользователь замечает, что с программой что-то не так, уже после возникновения побочных исключительных ситуаций. Справедливости ради стоит отметить, что чаще всего подобная ошибка допускается не намеренно, а из-за того, что программист слишком сильно полагается на современные среды разработки (Integrated Development Environment) и их средства завершения кода. Обычно, когда в коде пишется обращение к методу, который может вызвать исключительную ситуацию, среда разработки предлагает обработать этот вызов с помощью шаблона try – catch – finally. Программист перекладывает эту рутинную работу на IDE и возвращается к написанию основной функциональности приложения. Проблема состоит в том, что шаблон, используемый IDE, может выглядеть, как показано в листинге 3.

Листинг 3. Неправильный шаблон для обработки исключительной ситуации
1         try {
2             invokeMethodWithException();
3         } catch (MethodException me) {
4             //TODO add exception processing logic
5         }

В строке 3 в блоке catch среда разработки просто размещает комментарий, что сюда надо добавить код для обработки исключительной ситуации, не выполняя никаких действий по регистрации возникшей ситуации. Программист может пропустить этот момент, и в результате исключительная ситуация будет «скрыта» в недрах программы, что потом, скорее всего, приведет к возникновению других ошибок. Чтобы избежать подобных ошибок, необходимо изучить шаблоны, которые IDE применяет для обработки исключительных ситуаций, и, если в коде шаблона предлагается просто игнорировать возникшее исключение, то изменить его, как показано в листинге 4.

Листинг 4. Правильный шаблон для обработки исключительной ситуации
1         try {
2             invokeMethodWithException();
3         } catch (MethodException me) {
4             me.printStackTrace(System.err);
5         }

Как правильно организовать вывод исключительных ситуаций

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

Проще всего решить эту задачу с помощью инфраструктур, специально предназначенных для протоколирования жизненного цикла Java-приложений, например, библиотеки log4j. Конечно, в процессе разработки для регистрации событий можно использовать и стандартные потоки вывода, такие как System.out и System.err, однако в режиме эксплуатации подобная практика может привести к следующим проблемам:

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

Использование библиотеки log4j или аналогичной решает эти проблемы и облегчает анализ трассировок, так как в данном случае вместе с трассировкой автоматически будет выводиться дата и время возникновения исключительной ситуации и другая необходимая информация. Важно только учитывать, что выводить информацию об исключительной ситуации необходимо с соответствующим приоритетом (или уровнем протоколирования), как показано в листинге 5.

Листинг 5. Протоколирование исключительной ситуации с помощью библиотеки log4j
1         } catch (MethodException me) {
2             log.debug(me);                   //неправильно
3             log.error(me.getMessage(), me);  //правильно
4         }

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

Эскалация исключительных ситуаций на верхние уровни приложения

При организации обработки исключительных ситуаций программиста может подстерегать еще одна проблема, способная существенно осложнить поиск причины, непосредственно приведшей к возникновению исключения. И опять, своим возникновением эта проблема обязана чрезмерному доверию инструментам для разработки ПО и желанию программиста «сделать как лучше». Существуют несколько эмпирических правил обработки исключительных ситуаций, два из которых приведены ниже.

  • если исключительная ситуация не может быть обработана на том уровне приложения, где она возникла, то ее необходимо передать для обработки на более высокие уровни приложения;
  • с каждого уровня приложения должны «выбрасываться» исключительные ситуации соответствующих уровней.

Если объединить эти два правила вместе, то получится следующий вывод: исключительную ситуацию, которую нельзя обработать на текущем уровне приложения, необходимо поместить в исключительную ситуацию более подходящего типа и передать (эскалировать) ее на следующий уровень приложения. На практике это означает, что когда бизнес-компонент «ловит» исключительную ситуацию, связанную с ошибкой в базе данных, то он помещает исходное исключение в новое исключение бизнес-уровня и «перебрасывает» его для обработки наверх, как показано в листинге 6.

Листинг 6. Пример эскалации исключительной ситуации
1         } catch (DatabaseException de) {
2             log.error(de.getMessage(), de);
3             throw new BusinessException(de);
4         }

На следующем уровне этот алгоритм может повториться еще раз, что приведет к отправке исключения еще более высокого уровня и т.д. Хотя формально в листинге 6 нет никаких ошибок, подобный подход может серьезно затруднить анализ исключительных ситуаций, так как он приводит к «замусориванию» журнальных файлов. Корень проблемы находится в строке 3, где происходит вывод информации о возникшей исключительной ситуации, так как при подобном вызове происходит распечатка трассировки стека вызываемых методов. Если подобный код будет присутствовать на разных уровнях приложения, то можно предположить, что информация о трассировке будет выведена несколько раз. Из-за подобного дублирования будет сложно определить, где конкретно была изначально зафиксирована исключительная ситуация, да и вообще найти информацию о ней, так как размер журнальных файлов значительно увеличится.

Особенно это относится к корпоративным JEE приложениям, так как они обычно состоят из нескольких уровней и трассировки стека методов тоже содержат большое количество записей. Чтобы избежать попадания в такую ситуацию, необходимо грамотно организовать вывод информации об исключительной ситуации, например, так, как показано в листинге 7.

Листинг 7. Ограничение выводимой информации при эскалации исключения
1         } catch (DatabaseException de) {
2             // вывод только сообщения из исходной исключительной ситуации
3             log.error(de.getMessage()); 
4             throw new BusinessException(de);
5         }

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

Заключение

Поиск настоящей причины, приведшей к возникновению исключительной ситуации, вполне может оказаться довольно сложной задачей, особенно в многоуровневом корпоративном JEE приложении. И неправильно организованный вывод информации об исключительных ситуациях может еще больше усложнить эту работу. В этой статье были представлены несколько советов для начинающих программистов, призванных помочь при выполнении анализа причин возникновения исключительных ситуаций.

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

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=647051
ArticleTitle=Вселенная Java: Часть 1. Приемы для эффективной работы с исключительными ситуациями в Java
publish-date=04142011