English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Исключения являются мощным средством отладки, так как они отвечают на следующие три вопроса:
1. Что произошло?
2. Где это произошло?
3. Почему это произошло?
При эффективном использовании исключений тип исключения отвечает на вопрос "Что произошло?", трассировка исключения отвечает на вопрос "Где произошло?", а информация об исключении отвечает на вопрос "Почему это произошло?", если ваши исключения не отвечают на все эти вопросы, возможно, вы не используете их правильно.
Три принципа помогут вам максимально эффективно использовать исключения в процессе отладки: это:
1. Конкретно и ясно
2. Предварительное выбрасывание
3. Деферированный перехват
Чтобы объяснить три принципа эффективного управления исключениями, в этой статье рассматривается вымышленный класс управления личными финансами JCheckbook, который используется для ведения записей и отслеживания банковских операций, таких как взносы и выписки.
Конкретно и ясно
Java определяет иерархию классов исключений, которая начинается с Throwable, расширяется до Error и Exception, а Exception расширяется до RuntimeException. Как показано на рис. 1.
Эти четыре класса обобщены и не предоставляют много информации об ошибках, хотя инстанцирование этих классов grammatically правильно (например: new Throwable()), лучше всего рассматривать их как виртуальные базовые классы и использовать их более специфичные подклассы. Java предоставляет множество подклассов исключений, и если вам нужно что-то более конкретное, вы можете определить свои собственные исключительные классы.
Например: в пакете java.io определены подклассы Exception, такие как IOException, а более специфичными являются FileNotFoundException, EOFException и ObjectStreamException, которые являются подклассами IOException. Каждый из них описывает определенный тип I/O-ошибки: потеря файла, аномальный конец файла и неправильный поток объектов. Чем конкретнее исключение, тем лучше наша программа может ответить на вопрос: "Что пошло не так?"
При перехвате исключений важно быть максимально ясным. Например: JCheckbook может обработать FileNotFoundException, спросив пользователя о имени файла повторно, для EOFException он может продолжить работу, основываясь на информации, которую он прочитал до исключения. Если исключение ObjectStreamException, программа должна информировать пользователя о том, что файл поврежден, и предложить использовать резервную копию или другой файл.
Java упрощает явное перехват исключений, так как мы можем определить несколько блоков catch в одном блоке try, чтобы отдельно обработать каждый тип исключения.
File prefsFile = new File(prefsFilename); try{ readPreferences(prefsFile); } catch (FileNotFoundException e) { // уведомить пользователя о том, что указанный файл // не существует } catch (EOFException e) { // уведомить пользователя о том, что конец файла // была достигнута } catch (ObjectStreamException e) { // уведомить пользователя о том, что файл поврежден } catch (IOException e) { // уведомить пользователя о другом I/O // произошла ошибка }
JCheckbook использует несколько catch-блоков, чтобы предоставлять пользователям четкую информацию о захваченных исключениях. Например: если был захвачен FileNotFoundException, он может предложить пользователю specify another file. В некоторых случаях дополнительная работа по написанию кода, связанная с несколькими catch-блоками, может быть ненужной нагрузкой, но в этом примере дополнительный код действительно помогает программе предоставлять более удобный ответ пользователю.
Кроме трёх первых catch-блоков, последний catch-блок предоставляет пользователю более общую информацию об ошибке при возникновении IOException. Таким образом, программа может предоставлять как можно больше конкретной информации, но также иметь возможность обрабатывать неожиданные другие исключения.
Иногда разработчики захватывают обобщенные исключения и показывают имя класса исключения или печатают информацию о стеке исключений, чтобы "конкретизировать". Ни в коем случае не делайте этого! Пользователи, видящие java.io.EOFException или информацию о стеке исключений, только будут разочарованы, а не получат помощи. Следует захватывать конкретные исключения и предоставлять пользователям четкую информацию на "человеческом языке". Однако, информацию о стеке исключений можно распечатать в вашем файле журнала. Помните, исключения и информация о стеке исключений предназначены для разработчиков, а не для пользователей.
В конечном итоге, следует отметить, что JCheckbook не захватывает исключения в методе readPreferences(), а оставляет их捕获 и обработку на уровне пользовательского интерфейса, чтобы уведомить пользователя对话框ом или другим способом. Это называется "отложенным захватом", о чем будет сказано далее.
Раннее выбрасывание
Информация о стеке исключений предоставляет точный порядок вызовов методов, которые привели к возникновению исключения, включая имя класса, имя метода, имя файла кода и даже номер строки, чтобы точно определить место возникновения исключения.
java.lang.NullPointerException at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:103) at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:225) at jcheckbook.JCheckbook.startup(JCheckbook.java:116) at jcheckbook.JCheckbook.<init>(JCheckbook.java:27) at jcheckbook.JCheckbook.main(JCheckbook.java:318)
Данное выше показывает ситуацию, когда метод open() класса FileInputStream вызывает NullPointerException. Однако обратите внимание, что FileInputStream.close() является частью стандартной библиотеки Java, и проблема, вероятно, заключается в нашем коде, а не в API Java. Поэтому проблема, вероятно, возникла в одном из предыдущих методов, к счастью, он также был напечатан в информации о стеке.
К сожалению, NullPointerException - это один из самых малоинформативных (но наиболее часто встречающихся и разрушительных) исключений в Java. Оно вообще не упоминает о том, что нас интересует: где именно null. Поэтому нам приходится вернуться на несколько шагов, чтобы найти, где произошла ошибка.
Через постепенное отслеживание стека информации и проверку кода, мы можем определить, что причиной ошибки является передача пустого параметра имени файла в readPreferences(). Поскольку readPreferences() знает, что он не может обрабатывать пустые имена файлов, он немедленно проверяет это условие:
public void readPreferences(String filename) throws IllegalArgumentException{ if (filename == null) { throw new IllegalArgumentException("filename is null"); } //if //...выполнять другие операции... InputStream in = new FileInputStream(filename); //...читать файл предпочтений... }
Через предварительное выбрасывание исключений (также называемых "быстрое проваление"), исключения становятся ясными и точными. Стек информации немедленно отражает, что пошло не так (были переданы незаконные значения параметров), почему это произошло (имя файла не может быть пустым значением), и где это произошло (в начале readPreferences()). Таким образом, наша информация о стеке может предоставлять правдивую информацию:
java.lang.IllegalArgumentException: filename is null at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:207) at jcheckbook.JCheckbook.startup(JCheckbook.java:116) at jcheckbook.JCheckbook.<init>(JCheckbook.java:27) at jcheckbook.JCheckbook.main(JCheckbook.java:318)
Кроме того, содержащаяся в нем информация об исключении ("Имя файла пусто") делает информацию о возникновении исключения более богатой, предоставляя ясный ответ на вопрос, что пусто, что是我们之前 выброшенный NullPointerException не мог предоставить.
Рапидное_failure, достигаемое немедленным выбросом исключения при обнаружении ошибки, эффективно предотвращает создание не необходимых объектов или использование ресурсов, таких как файлы или сетевые подключения. Кроме того, можно избежать операций по очистке, связанных с открытием этих ресурсов.
Отложенное捕获
Одна из ошибок, которую могут допустить как новички, так и опытные программисты, это捕获 исключения до того, как программа способна их обработать. Компилятор Java косвенно способствует этому поведению, требуя, чтобы выявленные исключения должны быть捕获ены или выброшены. Натуральным шагом является немедленная упаковка кода в блок try и использование catch для захвата исключений, чтобы избежать ошибок компилятора.
Проблема в том, что что делать с исключением после его捕获а? Самым плохим решением является просто ничего не делать. Пустой блок catch равен тому, что исключение отправляется в черную дыру, и все информации о том, когда, где и почему произошла ошибка,永远丢失。Запись исключения в журнал все же лучше, по крайней мере, это留下了可查的记录. Но мы не можем рассчитывать на то, что пользователь будет читать или понимать файлы журналов и информацию об исключениях. Показывать диалоговое окно с сообщением об ошибке также не рекомендуется, потому что, хотя JCheckbook в настоящее время является десктопным приложением, мы планируем его转变 в веб-приложение на основе HTML. В этом случае показывать диалоговое окно显然 не является вариантом. В любом случае, независимо от HTML или C/S версии, конфигурационная информация читается с сервера, а сообщения об ошибках необходимо отображать веб-браузеру или клиентскому приложению. Метод readPreferences() должен учитывать эти будущие потребности при его разработке. С分离ом кода пользовательского интерфейса и логики программы мы можем повысить повторное использование нашего кода.
Преждевременное捕获 исключений перед условной обработкой обычно приводит к более серьезным ошибкам и другим исключениям. Например, если метод readPreferences() из предыдущего текста немедленно捕获 и записывает возможный FileNotFoundException, код может выглядеть так:
public void readPreferences(String filename){ //... InputStream in = null; // НЕ ДЕЛАЙ ЭТО!!! try{ in = new FileInputStream(filename); } catch (FileNotFoundException e) { logger.log(e); } in.read(...); //... }
Ниже приведен код, который захватывает FileNotFoundException, когда он не имеет возможности восстановиться из него. Если файл не найден, очевидно, что метод не сможет его прочитать. Что会发生, если readPreferences() будет требовать чтения несуществующего файла? Naturally, FileNotFoundException будет зарегистрирован, и если мы посмотрим на файл журнала, мы узнаем об этом. Однако что произойдет, когда программа попытается прочитать данные из файла? Поскольку файла нет, переменная in будет пуста, и будет выброшен NullPointerException.
Во время отладки программы интуиция подсказывает нам, что следует проверять информацию в конце журнала. Это будет NullPointerException, и это исключение очень неопределенно. Информация об ошибке может ввести в заблуждение, что именно пошло не так (на самом деле это FileNotFoundException, а не NullPointerException), и даже误导ить нас о месте возникновения ошибки. Проблема действительно находится на несколько строк выше места, где был вызван NullPointerException, и между ними может быть несколько вызовов методов и уничтожение классов. Наше внимание отвлекается этой "маленькой рыбкой" от истинной проблемы, и только когда мы смотрим на журнал назад, мы находим源头 проблемы.
Если readPreferences() действительно не должен захватывать эти исключения, то что он должен делать? Это может показаться необычным, но обычно наиболее подходящим действием является ничего не делать, не захватывать исключение сразу. Передайте ответственность вызовщику readPreferences(), чтобы он мог исследовать подходящее решение для отсутствия файла конфигурации. Он может предложить пользователю указать другой файл, использовать значение по умолчанию, а в крайнем случае предупреждать пользователя и завершить программу.
Одним из способов передачи ответственности за обработку исключений вверх по цепочке вызовов является указание исключений в разделе throws метода. При указании возможных исключений, старайтесь быть как можно более конкретными. Это помогает определить типы исключений, которые должны быть известны и готовы к обработке программами, которые вызывают ваш метод. Например, версия readPreferences() с "отложенным захватом" может выглядеть так:
public void readPreferences(String filename) throws IllegalArgumentException, FileNotFoundException, IOException{ if (filename == null) { throw new IllegalArgumentException("filename is null"); } //if //... InputStream in = new FileInputStream(filename); //... }
Технически, единственным исключением, который нам нужно объявить, является IOException, но мы явно указываем, что метод может выбрасывать FileNotFoundException. IllegalArgumentException не обязателен для объявления, так как это неявное исключение (подкласс RuntimeException). Однако мы объявляем его для документирования нашего кода (эти исключения также должны быть указаны в JavaDocs методов).
Конечно, в конечном итоге ваша программа должна ловить исключения, иначе она неожиданно прекратит работу. Но хитрость заключается в том, чтобы ловить исключения на подходящем уровне, чтобы ваша программа могла либо значимо восстановиться из исключений и продолжить работу, не вызывая более глубоких ошибок; либо предоставлять пользователям четкую информацию, включая руководство по восстановлению из ошибок. Если ваше решение не справляется с этим, лучше не обрабатывать исключения, а оставить их для последующего захвата и обработки на соответствующем уровне.
Обобщение
Опытные разработчики знают, что основные трудности при отладке программ не заключаются в исправлении дефектов, а в поиске мест их藏ования среди的大量 кода. Следуя трем принципам, изложенным в этой статье, вы сможете использовать свои исключения для отслеживания и устранения дефектов,从而使 ваши программы более устойчивыми и дружественными к пользователям. Это все, что содержит эта статья, и я надеюсь, что она поможет вам в изучении или работе.
Заявление: содержимое этой статьи взято из Интернета, авторские права принадлежат соответствующему автору. Контент предоставлен пользователями Интернета, самостоятельно загружен, сайт не обладает правами собственности, не был редактирован вручную и не несет ответственности за связанные с этим юридические последствия. Если вы обнаружите подозрительное нарушение авторских прав, пожалуйста, отправьте письмо по адресу: notice#oldtoolbag.com (во время отправки письма замените # на @),并提供相关证据. При подтверждении факта нарушения мы незамедлительно удалим спорное содержимое.