Проблема
Язык Java имеет четко определенный механизм обработки исключений, но требуется некоторое время, чтобы научиться эффективно его использовать, не разочаровывая ни пользователей, ни специалистов технической поддержки.
Решение
Язык Java предлагает иерархию исключений, которая обеспечивает значительную гибкость при правильном использовании. Android предлагает несколько механизмов, включая диалоговые окна и тосты, для уведомления пользователя об ошибках. Разработчик приложений для платформы Android должен ознакомиться с этими механизмами и научиться эффективно их использовать.
Обсуждение
В языке Java существуют две категории исключений (которые фактически являются производными от класса Throwable
, родительского класса по отношению к классу Exception): проверяемые и непроверяемые. Авторы платформы Java Standard Edition, очевидно, хотели заставить программистов признать тот факт, что что-то можно обнаружить во время компиляции, а что-то нет. Например, если вы устанавливаете настольное приложение на большое количество персональных компьютеров, скорее всего, диск на некоторых из них будет почти заполненным, и попытка сохранить данные на них может завершиться неудачно. Между тем на других компьютерах файл, зависящий от приложения, может отсутствовать не из-за ошибки программиста, а из-за ошибки пользователя, случайности файловой системы, разрыва кабеля или чего-то еще. Таким образом, категория IOException
была создана как проверяемое исключение, которое программист обязан проверить либо с помощью инструкции try-catch
внутри метода, использующего файл, либо с помощью инструкции throws
в определении метода. Общее правило, которое знают все хорошо обученные разработчики на языке Java, формулируется следующим образом.
Класс
Throwable
является корнем иерархии генерируемых исключений. КлассException и все его подклассы, за исключением класса
RuntimeException
иливсех его подклассов, являются проверяемыми исключениями. Все остальные
являются непроверяемыми.
Это означает, что класс Error
и все его подклассы являются непроверяемыми (рис. 1). Например, если вы получаете объект класса VMError
, это означает, что во время выполнения возникла ошибка. В качестве прикладного программиста вы ничего не можете сделать. К подклассам класса RuntimeException
относится, в частности, класс с очень длинным именем ArraylndexOutOfBoundsException
.
Его дружественные классы являются непроверяемыми, потому что ответственность за эти исключения во время разработки и их тестирование лежит на вас.
Рис. 1. Иерархия генерируемых исключений
Где перехватывать исключения
Использование проверяемых исключений привело к тому, что многие ранние разработчики программ на языке Java писали код, который был испещрен блоками try-catch
, отчасти потому, что важность использования инструкции throws
недостаточно подчеркивалась в некоторых учебных программах и книгах. Поскольку сам язык Java перенес акцент на корпоративную работу и появились новые каркасы, такие как Spring, Hibernate и JPA, которые делают упор на использовании непроверяемых исключений, эта первоначальная позиция изменилась. В настоящее время общепризнано, что перехватывать исключения желательно как можно ближе к пользователю. Не следует пытаться выполнять обработку ошибок в коде, предназначенном для повторного использования, — в библиотеках и даже многократно запускаемых приложениях. Он может выполнить лишь трансляцию исключения (exception translation), т.е. превращение исключения, специфичного для технологии (и обычно проверяемого), в обобщенное непроверяемое исключение. Образец показан в примере 1.
Пример 1. Трансляция исключения
public class ExceptionTranslation {
public String readTheFile(String f) {
try (BufferedReader is = new BufferedReader(new FileReader(f))) {
String line = is.readLine();
return line;
} catch (FileNotFoundException fnf) {
throw new RuntimeException("Could not open file " + f, fnf);
} catch (IOException ex) {
throw new RuntimeException("Problem reading file " + f, ex);
}
}
}
Обратите внимание, что до появления версии Java 7, для того чтобы закрыть файл, вам пришлось бы написать явную инструкцию finally
:
} finally {
if (is != null) {
try {
is.close();
} catch(IOException grr) {
throw new RuntimeException("Error on close of " + f, grr);
}
}
}
}
Подчеркнем также, что использование проверяемых исключений загромождает даже этот код: функция is.close()
практически никогда не может дать сбой, но поскольку вы хотите включить ее в блок finally (чтобы убедиться, что файл был открыт, но что-то пошло не так), необходимо предусмотреть дополнительную внешнюю инструкцию try-catch. Таким образом, проверяемые исключения (чаще всего) раздражают, и их следует избегать в новых интерфейсах API и заменять непроверяемыми исключениями, если это необходимо.
Существует противоположная точка зрения, поддерживаемая официальным сайтом Oracle и другими. В комментарии на веб-сайте читатель Ал Саттон (А1 Sutton) указывает следующее.
Существуют проверяемые исключения, заставляющие разработчиков признать, что может возникнуть ошибочное условие и что они подумали о том, как с ним справиться. Во многих случаях, помимо записи в журнале и восстановления, сделать можно очень мало, но разработчик все же признает, что он предусмотрел обработку этого типа ошибок. В приведенном примере... код останавливает вызовы метода, если файла не существует (и, возможно, его необходимо повторно загрузить), или возникает проблема с его чтением (и, следовательно, файл существует, но не читается). Они представляют собой два разных типа ошибок.
Платформа Android, желая быть верной интерфейсу Java API, имеет несколько таких проверяемых исключений (включая те, которые показаны в примере), поэтому их следует обрабатывать одинаково.
Что делать с исключениями?
Исключения почти всегда должны выдавать сообщения. Когда я вижу код, который перехватывает исключения и ничего с ними не делает, я прихожу в отчаяние. Ну хотя бы сообщите об их появлении (но не делайте одновременно запись в журнале и трансляцию или повторное генерирование!). Все исключения должны, как следует из названия, служить индикаторами исключительных условий. Поскольку на устройстве Android отсутствует системный администратор или оператор консоли, сообщать об исключительных условиях необходимо пользователю.
Необходимо подумать о том, как сообщать об исключениях: с помощью диалоговых окон или тостов. Обработка исключений на мобильном устройстве отличается от обработки на настольном компьютере. Пользователь может управлять автомобилем или другим оборудованием, общаться с людьми и т.д., поэтому вы не должны предполагать, что он полностью сконцентрирован на приложении.
Я знаю, что большинство примеров, даже в этой книге, используют тосты, потому что они требуют меньше кодирования, чем диалоговые окна. Но помните, что тост отображается на экране только в течение нескольких секунд; моргнув, вы можете его пропустить. Если пользователю необходимо что-то сделать, чтобы устранить проблему, необходимо использовать диалоговые окна.
Тосты просто всплывают, а затем забываются. Диалоговые окна требуют от пользователя либо подтверждения исключения, либо разрешения на продолжение работы, которое иногда может стоить денег (например, подключение доступа к Интернету для запуска приложения, которому необходимо загрузить фрагменты карты).
Используйте тосты, чтобы выводить на экран неважную информацию; для отображения важной информации и получения подтверждения используйте диалоговые окна.