Другие суммировали довольно хорошо, почему бросать рано . Позвольте мне сконцентрироваться на том, зачем ловить позднюю часть вместо этого, для которой я не видел удовлетворительного объяснения моего вкуса.
ТАК ПОЧЕМУ ИСКЛЮЧЕНИЯ?
Похоже, что возникла путаница, почему исключения существуют в первую очередь. Позвольте мне поделиться здесь большим секретом: причина исключений и их обработка ... АБСТРАКЦИЯ .
Вы видели такой код:
static int divide(int dividend, int divisor) throws DivideByZeroException {
if (divisor == 0)
throw new DivideByZeroException(); // that's a checked exception indeed
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
try {
int res = divide(a, b);
System.out.println(res);
} catch (DivideByZeroException e) {
// checked exception... I'm forced to handle it!
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Это не то, как исключения должны быть использованы. Код, подобный приведенному выше, существует в реальной жизни, но он скорее аберрация и действительно исключение (каламбур). Например, определение деления , даже в чистой математике, является условным: всегда «код вызывающей стороны» должен обрабатывать исключительный случай нуля, чтобы ограничить входную область. Это ужасно. Это всегда боль для звонящего. Тем не менее, для таких ситуаций естественным путем является паттерн check-then-do :
static int divide(int dividend, int divisor) {
// throws unchecked ArithmeticException for 0 divisor
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
if (b != 0) {
int res = divide(a, b);
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
В качестве альтернативы вы можете использовать полную команду в стиле ООП, например:
static class Division {
final int dividend;
final int divisor;
private Division(int dividend, int divisor) {
this.dividend = dividend;
this.divisor = divisor;
}
public boolean check() {
return divisor != 0;
}
public int eval() {
return dividend / divisor;
}
public static Division with(int dividend, int divisor) {
return new Division(dividend, divisor);
}
}
static void doDivide() {
int a = readInt();
int b = readInt();
Division d = Division.with(a, b);
if (d.check()) {
int res = d.eval();
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Как видите, код вызывающей стороны несет бремя предварительной проверки, но не выполняет обработки исключений после. Если ArithmeticException
когда-либо приходит от вызова divide
или eval
, то именно ВЫ должны обрабатывать исключения и исправлять ваш код, потому что вы забыли check()
. По тем же причинам поймать NullPointerException
почти всегда неправильно.
Теперь есть некоторые люди , которые говорят , что они хотят видеть исключительные случаи в / метод сигнатуры функции, то есть явно продлить выходной домен . Именно они предпочитают проверенные исключения . Конечно, изменение домена вывода должно заставить любой код прямого вызова адаптироваться, и это действительно будет достигнуто с проверенными исключениями. Но вам не нужны исключения для этого! Именно поэтому у вас есть Nullable<T>
общие классы , классы регистра , алгебраические типы данных и типы союзов . Некоторые OO люди могут даже предпочесть возвращаться null
для простых ошибок, таких как это:
static Integer divide(int dividend, int divisor) {
if (divisor == 0) return null;
return dividend / divisor;
}
static void doDivide() {
int a = readInt();
int b = readInt();
Integer res = divide(a, b);
if (res != null) {
System.out.println(res);
} else {
System.out.println("Nah, can't divide by zero. Try again.");
}
}
Технически исключения могут быть использованы для целей, подобных описанным выше, но здесь есть смысл: исключений для такого использования не существует . Исключения составляют про абстракция. Исключение составляют косвенные указания. Исключения позволяют расширять «конечный» домен, не нарушая прямых клиентских контрактов и откладывая обработку ошибок до «где-то еще». Если ваш код генерирует исключения, которые обрабатываются в прямых вызывающих программах одного и того же кода, без каких-либо промежуточных уровней абстракции, то вы делаете это НЕПРАВИЛЬНО
КАК ПОЗДАТЬ ПОЗДНО?
Итак, мы здесь. Я изложил свой способ показать, что использование исключений в вышеупомянутых сценариях - это не то, как исключения должны использоваться. Однако существует подлинный вариант использования, где абстракция и косвенность, предлагаемые обработкой исключений, являются обязательными. Понимание такого использования поможет также понять рекомендацию с опозданием на вылов .
Этот вариант использования: Программирование против абстракций ресурсов ...
Да, бизнес-логика должна быть запрограммирована против абстракций , а не конкретных реализаций. Код «разводки» IOC верхнего уровня создаст конкретные реализации абстракций ресурса и передаст их бизнес-логике. Здесь нет ничего нового. Но конкретные реализации этих абстракций ресурсов могут потенциально генерировать свои собственные специфические для реализации исключения , не так ли?
Так кто же может обработать эти специфичные для реализации исключения? Можно ли вообще обрабатывать какие-либо специфичные для ресурса исключения в бизнес-логике? Нет, это не так. Бизнес-логика запрограммирована против абстракций, что исключает знание этих специфических для реализации деталей исключений.
«Ага!», Вы можете сказать: «Но именно поэтому мы можем создавать подклассы исключений и создавать иерархии исключений» (см. Мистер Спринг !). Позвольте мне сказать вам, что это ошибка. Во-первых, в каждой разумной книге об ООП говорится, что конкретное наследование является плохим, однако этот основной компонент JVM, обработка исключений, тесно связан с конкретным наследованием. По иронии судьбы, Джошуа Блох, возможно, не написал бы свою книгу «Эффективное Java» прежде, чем он смог бы получить опыт работы с рабочей JVM, не так ли? Это скорее книга «извлеченных уроков» для следующего поколения. Во-вторых, и что еще более важно, если вы поймали исключение высокого уровня, то как вы собираетесь его обрабатывать?PatientNeedsImmediateAttentionException
: мы должны дать ей таблетку или ампутировать ее ноги !? Как насчет оператора switch для всех возможных подклассов? Там идет ваш полиморфизм, там идет абстракция. Вы поняли.
Так, кто может обращаться с исключениями конкретного ресурса? Это должен быть тот, кто знает конкременты! Тот, кто создал ресурс! Код "проводка" конечно! Проверь это:
Бизнес-логика закодирована против абстракций ... НЕТ ОБРАБОТКИ БЕТОННЫХ РЕСУРСОВ!
static interface InputResource {
String fetchData();
}
static interface OutputResource {
void writeData(String data);
}
static void doMyBusiness(InputResource in, OutputResource out, int times) {
for (int i = 0; i < times; i++) {
System.out.println("fetching data");
String data = in.fetchData();
System.out.println("outputting data");
out.writeData(data);
}
}
Между тем где-то еще конкретные реализации ...
static class ConstantInputResource implements InputResource {
@Override
public String fetchData() {
return "Hello World!";
}
}
static class FailingInputResourceException extends RuntimeException {
public FailingInputResourceException(String message) {
super(message);
}
}
static class FailingInputResource implements InputResource {
@Override
public String fetchData() {
throw new FailingInputResourceException("I am a complete failure!");
}
}
static class StandardOutputResource implements OutputResource {
@Override
public void writeData(String data) {
System.out.println("DATA: " + data);
}
}
И, наконец, код подключения ... Кто обрабатывает конкретные исключения ресурсов? Тот, кто знает о них!
static void start() {
InputResource in1 = new FailingInputResource();
InputResource in2 = new ConstantInputResource();
OutputResource out = new StandardOutputResource();
try {
ReusableBusinessLogicClass.doMyBusiness(in1, out, 3);
}
catch (FailingInputResourceException e)
{
System.out.println(e.getMessage());
System.out.println("retrying...");
ReusableBusinessLogicClass.doMyBusiness(in2, out, 3);
}
}
Теперь терпите меня. Приведенный выше код является упрощенным. Вы можете сказать, что у вас есть корпоративное приложение / веб-контейнер с несколькими областями управляемых ресурсов контейнера IOC, и вам нужны автоматические повторные попытки и повторная инициализация ресурсов области сеанса или запроса и т. Д. Логика разводки в областях более низкого уровня может быть предоставлена абстрактным фабрикам для создавать ресурсы, поэтому не зная точных реализаций. Только области более высокого уровня действительно будут знать, какие исключения могут генерировать эти ресурсы более низкого уровня. Теперь держись!
К сожалению, исключения допускают только косвенное обращение к стеку вызовов, и разные области действия с разным количеством элементов обычно работают в нескольких разных потоках. Нет способа общаться через это, за исключением. Нам нужно что-то более мощное здесь. Ответ: асинхронная передача сообщений . Поймать каждое исключение в корне области нижнего уровня. Ничего не игнорируйте, не позволяйте ничему ускользнуть. Это закроет и удалит все ресурсы, созданные в стеке вызовов текущей области. Затем распространяйте сообщения об ошибках в области выше, используя очереди / каналы сообщений в процедуре обработки исключений, пока не достигнете уровня, на котором известны конкреции. Это парень, который знает, как справиться с этим.
СУММА СУММАРУМ
Таким образом, согласно моей интерпретации, ловить поздно означает ловить исключения в наиболее удобном месте, где вы больше не нарушаете абстракцию . Не лови слишком рано! Поймать исключения на уровне, где вы создаете конкретные исключения, выбрасывая экземпляры абстракций ресурса, уровень, который знает сокращения абстракций. Слой проводки.
НТН. Удачного кодирования!