Effective Java: Exceptions

Продолжаем. Сегодня речь о 9-й главе «Exceptions».


Item 57. Используйте exception-ы только для исключительных (exceptional) ситуаций.

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

// Horrible abuse of exceptions. Don't ever do this!
try {
    int i = 0;
    while(true)
        range[i++].climb();
} catch(ArrayIndexOutOfBoundsException e) {
}

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

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


Item 58. Используйте проверяемые исключения (checked exceptions) в поправимых ситуациях и непроверяемые исключения (runtime exceptions) при программных ошибках.

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

  • Принято считать, что класс Error и его подклассы используются только виртуальной машиной, и наследоваться от них не надо - вместо этого использовать RuntimeException.
  • Не имеет смысла напрямую имплементировать Throwable, не наследуясь от какого-либо Exception-а. Поведение будет такое же, как и у checked exceptions.
  • Exception-ы - полноценные объекты, для которых можно и нужно объявлять дополнительные методы, предоставляющие информацию об исключении.


Item 59. Избегайте лишних проверяемых исключений (checked exceptions).

Злоупотребление проверяемыми исключениями приводит к неудобствам при использовании вашего API. Если исключение можно предотвратить проверкой специальных условий (как метод hasNext() класса Iterator), либо клиент не сможет предпринять никаких полезных действий при возникновении исключения, то использование runtime exception - наилучший выбор.


Item 60. Предпочитайте стандартные классы-исключения.

Применение стандартных исключений полезно хотя бы потому, что все их знают, и использование вашего API будет таким образом упрощено. Самые часто применяемые исключения из стандартной библиотеки: IllegalArgumentException, IllegalStateException, NullPointerException, IndexOutOfBoundsException, ConcurrentModificationException, UnsupportedOperationException. Важно не забывать, что exception должен соответствовать исключительной ситуации не только по названию, но и по семантике, описанной в документации к исключению. Также бывает полезно наследоваться от стандартных исключений для добавления в них какой-либо полезной информации.


Item 61. Кидайте исключения, соответствующие уровню абстракции.

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

/**
 * Returns the element at the specified position in this list.
 * @throws IndexOutOfBoundsException if the index is out of range
 * ({@code index < 0 || index >= size()}).
 */
public E get(int index) {
    ListIterator<E> i = listIterator(index);
    try {
        return i.next();
    } catch(NoSuchElementException e) {
        throw new IndexOutOfBoundsException("Index: " + index);
    }
}

Это называется exception translation idiom. Особая её форма - exception chaining - когда низкоуровневое исключение не отбрасывается, а сохраняется в поле cause высокоуровнего исключения:

// Exception Chaining
try {
    ... // Use lower-level abstraction to do our bidding
} catch (LowerLevelException cause) {
    throw new HigherLevelException(cause);
}

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


Item 62. Документируйте кидаемые исключения.

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

Описывайте каждое проверяемое исключение (checked exception), которое может быть кинуто из ваших методов, используйте для этого javadoc-тег @throws. Непроверяемые исключения тоже важно упоминать в документации, но не с тегом @throws, а просто в описании метода. Конечно, описать всевозможные runtime exceptions далеко не всегда представляется возможным. Всё это относится как к конкретным методам, так и к абстрактным и методам в интерфейсах.

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


Item 63. Включайте в исключения информацию об ошибках.

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


Item 64. Стремитесь к атомарности сбоев (failure atomicity).

Любое задокументированное исключение, сгенерированное методом объекта, должно оставлять объект в рабочем состоянии - это и называется failure atomicity. Если добиться такого эффекта слишком сложно или невозможно, нужно ясно заявить об этом в документации.

Для неизменяемых (immutable) инстансов failure atomicity гарантирована по определению. Добиться её в остальных случаях можно одним из следующих способов (по убыванию популярности):

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


Item 65. Не игнорируйте исключения.

Кажется, это очевидно. Исключения придуманы для того, чтобы заставить программиста учитывать их и выполнять какие-либо действия при их возникновении, а не игнорировать их. В самом крайнем случае, когда никакие действия и правда не нужны, необходимо в catch-блоке написать комментарий, почему exception проигнорирован.


Впереди две последние главы - о многопоточности и сериализации. Не переключайте канал.

Комментариев нет:

Отправить комментарий