Главная » Java, Советы » Используйте исключения лишь в исключительных ситуациях

0

 

Однажды, если вам не повезет, вы сделаете ошибку в программе, например, такую:

// Неправильное использование исключений. Никогда так не делайте!

try {

int i = 0;

while(true)

а[i++]. f();

} catch(ArraylndexOutOfBoundsException е) {  }

 

Что делает этот код? Изучение кода не вносит полной ясности, и это достаточная причина, чтобы им не пользоваться. Здесь приведена плохо продуманная идиома для циклического перебора элементов в массиве. Когда производится попытка обращения к первому элементу за пределами массива, бесконечный цикл завершается инициированием исключительной ситуации ArraylndexOutOfBoundException, ее перехватом и последующим игнорированием. Предполагается, что это эквивалентно стандартной идиоме цикла по массиву, которую узнает любой программист java:

for (int i = 0; i < a.length; i++)

a[i].f();

 

 

Но почему же кто-то выбрал идиому, использующую исключения, вместо другой, испытанной и правильной? Это вводящая в заблуждение попытка улучшить производительность, которая исходит из ложного умозаключения, что, поскольку виртуальная машина проверяет границы при всех обращениях к массиву, обычная проверка на завершение цикла (i < a.length) избыточна и ее следует устранить. В этом рассуждении неверны три момента:

 

·         Так как исключения создавались для применения в исключительных условиях, лишь очень немногие реализации JVM пытаются их оптимизировать (если таковые есть вообще). Обычно создание, инициирование и перехват исключения дорого обходится системе

 

·         Размещение кода внутри блока try-catch препятствует выполнению определенных процедур оптимизации, которые в противном случае могли бы быть исполнены в современных реализациях jVM.

 

·         Стандартная идиома цикла по массиву вовсе не обязательно приводит к выполнению избыточных проверок, в процессе оптимизации некоторые современные реализации jVM отбрасывают их.

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

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

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

159Этот принцип относится также к проектированию АРI. Хорошо спроектированный API не должен заставлять своих клиентов использовать исключения для обычного управления потоком вычислений. Если в классе есть метод, зависящий от состояния (state-dependent), который может быть вызван лишь при выполнении определенных непредсказуемых условий, то в этом же классе, как правило, должен присутствовать отдельный метод, проверяющий состояние (state-testing), который показывает, можно ли вызывать первый метод. Например, класс Iterator имеет зависящий от состояния метод next, который возвращает элемент для следующего прохода цикла, а также соответствующий метод проверки состояния hasNext. Это позволяет применять для просмотра коллекции в цикле следующую стандартную идиому:

for (Iterator i = collection. iterator(); i. hasNext(); ) {

 Foo foo = (Foo) i.next();  }

 

Если бы в классе Iterator не было бы метода hasNext, клиент был бы вынужден использовать следующую конструкцию:

 // Не пользуйтесь этой отвратительной идиомой

 // для просмотра коллекции в цикле!

 

try {

Iterator i = collection.iterator();

 while(true) {

Foo foo = (Foo) i.next();

}

} catch (NoSuchElementException е) {  }

 

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

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

Приведем некоторые рекомендации, которые помогут вам сделать выбор между методом проверки состояния и особым возвращаемым значением. Если к объекту возможен одновременный доступ без внешней синхронизации или если смена его СОСТОJШИЙ инициируется извне, может потребоваться прием с особым возвращаемым

 

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

 

Источник: Джошуа Блох, Java TM Эффективное программирование, Издательство «Лори»

По теме:

  • Комментарии