Інші досить добре підсумували, чому кидати рано . Дозвольте зосередитися на тому, чому натомість піймати пізню частину, для якої я не бачив задовольняючого пояснення свого смаку.
ЧОМУ ЧОМУ ВИКОНАННЯ?
Здається, існує велика плутанина навколо того, чому в першу чергу існують винятки. Дозвольте мені поділитися тут великою таємницею: причина винятків та поводження з винятками - ... РЕЗУЛЬТАТ .
Ви бачили такий код:
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.");
}
}
Ось як не слід використовувати винятки. Кодекси, як описано вище, існують у реальному житті, але вони є скоріше відхиленням і насправді є винятком (каламбур). Наприклад, визначення поділу , навіть у чистій математиці, є умовним: для обмеження вхідної області завжди потрібен "код абонента", який повинен обробляти винятковий випадок нуля. Це некрасиво. Це завжди біль для абонента. Проте для таких ситуацій закономірний спосіб " перевірити тоді" - це :
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.");
}
}
Крім того, ви можете пройти повний командос на стилі OOP, як це:
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()
. З подібних причин ловити a NullPointerException
майже завжди неправильно.
Зараз є деякі люди, які заявляють, що хочуть бачити виняткові випадки у підписі методу / функції, тобто явно розширити вихідний домен . Саме вони віддають перевагу перевіреним виняткам . Звичайно, зміна вихідного домену повинно змусити будь-який код прямого абонента адаптуватися, і це дійсно було б досягнуто за допомогою перевірених винятків. Але для цього вам не потрібні винятки! Ось чому у вас є Nullable<T>
загальні класи , класи справ , алгебраїчні типи даних та типи об'єднання . Деякі люди можуть навіть віддавати перевагу таким 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.");
}
}
Технічно винятки можуть використовуватися для таких цілей, як вище, але тут справа: винятки для такого використання не існують . Виняток становить про абстракція. Виняток стосується непрямості. Винятки дозволяють розширити домен "результат", не порушуючи прямих клієнтських контрактів, і не відклавши обробку помилок на "деінде". Якщо ваш код видаляє винятки, які обробляються у прямих абонентів того самого коду, без шарів абстрагування між ними, то ви це робите WRONG
ЯК ЗЛІТИТИ КРАЙ?
Отже, ось ми. Я аргументував свій шлях до того, щоб показати, що використання виключень у вищезазначених сценаріях не є способом використання винятків. Однак існує справжній випадок використання, коли абстрагування та опосередкованість, запропоновані обробкою виключень, є незамінними. Розуміння такого використання також допоможе зрозуміти запізнілу рекомендацію.
Цей випадок використання: Програмування проти абстракції ресурсів ...
Так, бізнес-логіка повинна бути запрограмована проти абстракцій , а не конкретних реалізацій. Код "проводки" верхнього рівня МОК дозволить створити конкретні реалізації ресурсів і передати їх у ділову логіку. Нічого нового тут немає. Але конкретні реалізації цих ресурсів можуть потенційно спричинити винятки щодо їх власних впроваджень , чи не так?
То хто може поводитись із тими винятками щодо впровадження? Чи можливо в цій бізнес-логіці взагалі обробляти будь-які винятки, що стосуються конкретних ресурсів? Ні, це не так. Бізнес-логіка запрограмована проти абстракцій, що не виключає знань про конкретні деталі винятку щодо впровадження.
"Ага!", Ви можете сказати: "але тому ми можемо підкласифікувати винятки та створювати ієрархії винятків" (перевірити містера Весна !). Дозвольте сказати, це помилка. По-перше, кожна розумна книга про ООП говорить, що конкретна спадщина є поганою, але якось ця основна складова JVM, обробка винятків, тісно пов'язана з конкретним успадкуванням. За іронією долі, Джошуа Блох не міг написати свою « Ефективну книгу Java», перш ніж він зможе отримати досвід роботи з JVM, чи не так? Це скоріше книга "засвоєних уроків" для наступного покоління. По-друге, і що ще важливіше, якщо ви потрапили на виняток на високому рівні, то як ви збираєтесь це ВІДРАТИ?PatientNeedsImmediateAttentionException
: ми повинні дати їй таблетку чи ампутацію ніг !? Як щодо заяви переключення для всіх можливих підкласів? Там іде ваш поліморфізм, іде абстракція. Ви зрозуміли справу.
То хто може обробляти винятки, що стосуються конкретних ресурсів? Це повинен бути той, хто знає конкременти! Той, хто створив ресурс! Код "проводки" звичайно! Перевір це:
Ділова логіка, кодована проти абстракцій ... НЕ БЕТОННЯ РЕЗУЛЬТАТА ПОМИЛКИ ПОШУКУВАННЯ!
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);
}
}
Тепер потерпіть зі мною. Наведений вище код є спрощеним. Ви можете сказати, що у вас є корпоративний додаток / веб-контейнер з декількома спектрами ресурсів, що керуються контейнерами МОК, і вам потрібні автоматичні повторні спроби та повторна ініціалізація ресурсів сеансу або запит на обсяг ресурсів тощо. створювати ресурси, тому не знаючи про точні реалізації. Тільки сфери вищого рівня справді знають, які винятки можуть спричинити ці ресурси нижчого рівня. Тепер тримайся!
На жаль, винятки дозволяють лише опосередковуватися над стеком викликів, а різні області застосування з їх різними кардиналами зазвичай працюють на декількох різних потоках. Ніякий спосіб спілкування через це за винятками. Тут нам потрібно щось більш потужне. Відповідь: передача повідомлення про асинхронізацію . Ловіть кожен виняток у корені області нижнього рівня. Нічого не ігноруйте, не дозволяйте нічого проскочити. Це закриє та розпорядиться всіма ресурсами, створеними у стеку викликів поточного діапазону. Потім розповсюджуйте повідомлення про помилки на масштаби вище, використовуючи черги / канали повідомлень у режимі обробки винятків, поки ви не досягнете рівня, на якому відомі конкременти. Це той хлопець, який вміє впоратися.
SUMMA SUMMARUM
Отже, згідно з моєю інтерпретацією, спіймати пізно - означає ловити винятки в найбільш зручному місці, де ви НЕ БУДЕТЕ НЕ БІЛЬШЕ РЕЗУЛЬТАТУ . Не ловіть занадто рано! Ловіть винятки на шарі, де ви створюєте конкретні винятки, що викидають екземпляри ресурсних абстракцій, шар, який знає конкременти абстракцій. Шар «проводки».
HTH. Щасливого кодування!