Расширение Throwable в Java - PullRequest
28 голосов
/ 03 мая 2010

Java позволяет вам создать совершенно новый подтип Throwable, например:

public class FlyingPig extends Throwable { ... }

Теперь, очень редко , я могу сделать что-то вроде этого:

throw new FlyingPig("Oink!");

и, конечно, в других местах:

try { ... } catch (FlyingPig porky) { ... }

Мои вопросы:

  • Это плохая идея? И если так, то почему?
    • Что можно было бы сделать, чтобы предотвратить этот подтип, если это плохая идея?
    • Поскольку это невозможно предотвратить (насколько я знаю), к каким катастрофам это может привести?
  • Если это не такая плохая идея, почему бы и нет?
    • Как вы можете сделать что-то полезное из того, что вы можете extends Throwable?

Предлагаемый сценарий # 1

Сценарий, в котором я действительно испытывал искушение сделать что-то подобное, имеет следующие свойства:

  • «Событие» - это то, что произойдет в конечном итоге . Ожидается . Совершенно определенно это не Error, и в этом нет ничего Exception -al, когда это происходит.
    • Поскольку ожидается , будет ожидаться catch. Это не будет "скользить" мимо чего-либо. Он не «уйдет» от попыток catch general Exception и / или Error.
  • «Событие» происходит крайне редко .
  • Когда это происходит, обычно есть глубокий след стека.

Так что, возможно, теперь ясно, что я пытаюсь сказать: FlyingPig - это результат исчерпывающего рекурсивного поиска.

Объект, который нужно найти, существует: вопрос только в том, чтобы найти его в большом море, которое является пространством поиска. Процесс поиска будет долгим, поэтому относительно дорогая стоимость обработки исключений незначительна. Фактически, традиционная альтернатива конструкции потока управления с использованием флага boolean isFound может быть более дорогой, потому что она должна проверяться непрерывно в течение всего процесса поиска, скорее всего на каждом уровне рекурсии. Эта проверка завершится неудачно в 99,99% случаев, но абсолютно необходимо распространить условие завершения. В некотором смысле, в то время как действует , проверка неэффективна !

Просто throw -ing FlyingPig, когда искомый объект найден, вам не нужно загромождать код с помощью управления boolean isFound флагом. Это не только очиститель кода, но из-за этого упущения он может работать быстрее.

Итак, подведем итог: выбор между этими двумя:

  • Традиционный подход управления потоком
    • Использовать boolean isFound, проверяется постоянно
    • 99,99% времени, чек - это «пустая трата», потому что он все равно будет false
    • Когда оно в конечном итоге становится true, вы прекращаете повторение и вам необходимо убедиться, что вы можете правильно отменить первоначальный вызов.
  • FlyingPig подход
    • Не беспокойтесь о boolean isFound.
    • Если найдено, просто throw new FlyingPig(); ожидается , поэтому для него будет catch.
    • Нет управления флагом boolean, нет потраченной впустую проверки, если вам нужно продолжать, нет бухгалтерии, чтобы вручную раскрутить рекурсию и т. Д.

Вопросы:

  • Является ли эта техника (ab) с использованием исключения допустимой? (Есть имя для этого?)
  • Если допустимо, должно ли FlyingPig extends Throwable или Exception нормально? (хотя в его обстоятельствах нет ничего исключительного?)

Ответы [ 7 ]

32 голосов
/ 03 мая 2010

Я бы сказал, что это действительно плохая идея. Большая часть кода реализована в предположении, что если вы поймаете Error и Exception, вы поймали все возможные исключения. И большинство учебников и учебников скажут вам то же самое. Создавая прямой подкласс Throwable, вы потенциально создаете всевозможные проблемы обслуживания и взаимодействия.

Я не могу представить себе веской причины для продления Throwable. Расширьте Exception или RuntimeException вместо.

РЕДАКТИРОВАТЬ - В ответ на предложенный сценарий ОП № 1.

Исключения - очень дорогой способ справиться с "нормальным" управлением потоком. В некоторых случаях речь идет о тысячах дополнительных инструкций, выполняемых для создания, выброса и перехвата исключения. Если вы собираетесь игнорировать принятую мудрость и использовать исключения для неисключительного управления потоком, используйте подтип Exception. Попытка притвориться чем-то - это «событие», а не «исключение», если объявить это подтипом Throwable, то ничего не добьется.

Однако ошибочно отождествлять исключение с ошибкой, ошибкой, ошибкой, чем угодно. И в нет ничего неправильного с представлением "исключительного события, которое не является ошибкой, ошибкой, ошибкой или чем-то еще", используя подкласс Exception. Ключ в том, что событие должно быть исключительное ; то есть необычно, случается очень редко, ...

Таким образом, FlyingPig не может быть ошибкой, но это не причина, чтобы не объявлять его как подтип Exception.

8 голосов
/ 03 мая 2010

Является ли эта методика (ab) с использованием исключения допустимой? (Есть имя для этого?)

На мой взгляд, это не очень хорошая идея:

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

Исключение не следует использовать для управления потоком . Если бы мне пришлось назвать эту технику, я бы назвал это запахом кода или анти-паттерном.

Смотри также:

Если действительно, FlyingPig расширяется Throwable или Exception нормально? (хотя в его обстоятельствах нет ничего исключительного?)

Могут быть ситуации, когда вы хотите поймать Throwable, чтобы не только поймать Exception , но и Error, но это редко, люди обычно не ловят Throwable. Но мне не удается найти ситуацию, в которой вы бы хотели выбросить подкласс Throwable.

И я также думаю, что расширение Throwable не делает вещи менее выглядящими «исключениями» , они заставляют его выглядеть хуже - что полностью противоречит намерению.

Итак, в заключение, если вы действительно хотите что-то бросить, бросьте подкласс Exception.

Смотри также:

7 голосов
/ 30 августа 2010

Вот сообщение в блоге от Джона Роуза, архитектора HotSpot:

http://blogs.oracle.com/jrose/entry/longjumps_considered_inexpensive

Речь идет о "злоупотреблении" исключениями для управления потоком. Немного другой вариант использования, но .. Короче говоря, это работает очень хорошо - если вы предварительно распределяете / клонируете свои исключения, чтобы предотвратить создание следов стека.

Я думаю, что эта техника оправдана, если «скрыта» от клиентов. IE, ваш FlyingPig никогда не сможет покинуть вашу библиотеку (все публичные методы должны транзитивно гарантировать, что она не будет выброшена). Один из способов гарантировать это - сделать это проверенным исключением.

Я думаю, что единственным оправданием для расширения Throwable является то, что вы хотите позволить людям передавать обратные вызовы с предложениями catch (Exception e), и вы хотите, чтобы ваш результат был проигнорирован ими. Я могу просто купить это ...

4 голосов
/ 03 мая 2010

Если вы можете обосновать, что отличает FlyingPig от Error и Exception, так что он не подходит как подкласс ни для одного из них, то нет ничего принципиально неправильного в его создании.

Самая большая проблема, о которой я могу думать, связана с прагматическим миром, иногда есть веские причины поймать java.lang.Exception. Ваш новый тип броска будет пролетать мимо блоков try-catch, у которых было все разумное ожидание подавления (или регистрации, переноса, чего угодно) каждой возможной нефатальной проблемы.

С другой стороны, если вы выполняете обслуживание старой системы, которая un оправданно подавляет java.lang.Exception, вы можете обмануть ее. (Предполагается, что искренняя просьба о времени, чтобы действительно исправить это, отклонена).

4 голосов
/ 03 мая 2010

Аннотация org.junit.Test включает класс None, который расширяет Throwable и используется в качестве значения по умолчанию для параметра аннотации expected.

2 голосов
/ 30 августа 2010

Играть! framework использует что-то подобное для обработки запросов. Обработка запросов проходит через множество уровней (маршрутизация, промежуточное ПО, контроллеры, рендеринг шаблонов), и на последнем уровне визуализированный HTML-код оборачивается в бросаемый и выбрасывается, который самый верхний слой захватывает, разворачивает и отправляет клиент. Таким образом, ни один из методов во многих задействованных слоях не должен явно возвращать объект ответа, и при этом им не нужно передавать объект ответа в качестве аргумента для распространения и изменения.

Я немного поверхностен в деталях. Вы можете просмотреть код Play! рамки для деталей.

2 голосов
/ 03 мая 2010

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

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

  • Использование наиболее конкретного возможного исключения - отличная идея. Это позволит вызывающей стороне по-разному обрабатывать различные типы исключений. Важно помнить об иерархии классов исключения, поэтому пользовательское исключение должно расширять другой тип Throwable, максимально приближенный к вашему исключению.
  • Расширение Throwable может быть слишком высокого уровня. Попробуйте вместо этого расширить Exception или RuntimeException (или исключение более низкого уровня, которое ближе к причине, по которой вы генерируете исключение). Имейте в виду разницу между RuntimeException и Exception.
  • Вызов метода, который генерирует Exception (или подкласс Exception), должен быть заключен в блок try / catch, который способен обрабатывать исключение. Это хорошо для случаев, когда вы ожидаете, что что-то пойдет не так, из-за обстоятельств, которые могут быть вне вашего контроля (например, из-за сбоя сети).
  • Вызов метода, который генерирует RuntimeException (или его подкласс), не нужно заключать в блок try / catch, который может обработать исключение. (Конечно, это может быть, но это не обязательно.) Это больше для исключений, которые действительно не следует ожидать.

Итак, предположим, у вас есть следующие классы исключений в базе кода:

public class Pig extends Throwable { ... }
public class FlyingPig extends Pig { ... }
public class Swine extends Pig { ... }
public class CustomRuntimeException extends RuntimeException { ... }

И некоторые методы

public void foo() throws Pig { ... }
public void bar() throws FlyingPig, Swine { ... }
// suppose this next method could throw a CustomRuntimeException, but it
// doesn't need to be declared, since CustomRuntimeException is a subclass
// of RuntimeException
public void baz() { ... } 

Теперь у вас может быть некоторый код, который вызывает эти методы, например так:

try {
    foo();
} catch (Pig e) {
    ...
}

try {
    bar();
} catch (Pig e) {
    ...
}

baz();

Обратите внимание, что когда мы вызываем bar(), мы можем просто поймать Pig, так как оба FlyingPig и Swine расширяются Pig. Это полезно, если вы хотите сделать то же самое для обработки любого исключения. Однако вы можете обращаться с ними по-другому:

try {
    bar();
} catch (FlyingPig e) {
    ...
} catch (Swine e) {
    ...
}
...