Винятки: навіщо кидати рано? Навіщо пізно ловити?


156

Існує багато відомих найкращих практик щодо поводження з винятками ізольовано. Я знаю "робити і чого не робити" досить добре, але все ускладнюється, коли справа стосується кращих практик або моделей у великих середовищах. "Кинь рано, лови пізно" - я чув уже багато разів, і це все ще мене бентежить.

Чому я повинен кидати рано і ловити пізно, якщо на низькому рівні шару кидається нульовий виняток вказівника? Чому я повинен ловити його на більш високому шарі? Для мене не має сенсу ловити винятки низького рівня на більш високому рівні, наприклад, бізнес-рівень. Здається, це порушує занепокоєння кожного шару.

Уявіть таку ситуацію:

У мене є служба, яка розраховує цифру. Для обчислення фігури служба отримує доступ до сховища для отримання необроблених даних та деяких інших служб для підготовки обчислення. Якщо на шарі пошуку даних щось пішло не так, чому я повинен перекинути DataRetrievalException на більш високий рівень? Навпаки, я вважаю за краще перетворити виняток на змістовний виняток, наприклад, CalculationServiceException.

Навіщо кидати рано, навіщо пізно ловити?


104
Ідея «ловити пізно» - це, а не ловити якомога пізніше, ловити якомога раніше. Якщо у вас є аналізатор файлів, наприклад, немає жодного сенсу обробляти файл, який не знайдено. Що ти маєш робити з цим, який твій шлях відновлення? Немає жодної, тому не ловіть. Перейдіть до свого лексема, що ви там робите, як ви одужаєте після цього, щоб програма могла продовжуватись? Це не може, нехай виняток проходить. Як ваш сканер може це впоратися? Не може, нехай проходить. Як виклик може впоратися з цим? Він може або спробувати інший файловий шлях, або попередити користувача, тому спіймайте.
Фоши

16
Дуже мало випадків, коли NullPointerException (я вважаю, що це означає NPE) коли-небудь повинен бути спійманий; по можливості, його слід уникати в першу чергу. Якщо у вас є NullPointerExceptions, у вас є зламаний код, який потрібно виправити. Це, швидше за все, також легко виправити.
Філ

6
Будь ласка, хлопці, перш ніж запропонувати закрити це питання як дублікат, переконайтесь, що відповідь на інше питання не дуже добре відповідає на це запитання.
Док Браун

1
(Citebot) today.java.net/article/2003/11/20/… Якщо це не походження цитати, будь ласка, надайте посилання на джерело, яке, на вашу думку, є найбільш вірогідною оригінальною цитатою.
rwong

1
Просто нагадування для тих, хто доходить до цього питання та займається розробкою Android. На Android, винятки повинні знаходитися та оброблятися локально - у тій самій функції, де і вперше. Це відбувається тому, що винятки не поширюються на обробники повідомлень - ваша програма буде знищена, якщо це станеться. Отже, вам не слід цитувати цю пораду, займаючись розробкою Android.
rwong

Відповіді:


118

На мій досвід, найкраще викидати винятки в тому місці, де трапляються помилки. Ви робите це, тому що саме ви знаєте найбільше про те, чому спрацювало виключення.

Оскільки виняток розкручує резервне копіювання шарів, виловлювання та повторне скидання - це хороший спосіб додати додатковий контекст до винятку. Це може означати, що викидаєте винятки іншого типу, але включайте оригінальний виняток, коли це робите.

Зрештою виняток досягне шару, де ви можете приймати рішення щодо потоку коду (наприклад, спонукання користувача до дії). Це пункт, коли слід нарешті обробити виняток і продовжити нормальне виконання.

З практикою та досвідом роботи з вашою кодовою базою стає досить легко судити, коли додати додатковий контекст до помилок, а де це найрозумніше насправді, нарешті обробити помилки.

Ловити → Знову

Зробіть це там, де ви зможете корисно додати більше інформації, яка б врятувала розробнику необхідність пропрацювати всі шари, щоб зрозуміти проблему.

Ловити → Ручка

Зробіть це там, де ви зможете приймати остаточні рішення про те, що є відповідним, але різним потоком виконання через програмне забезпечення.

Ловити → Помилка повернення

Хоча існують ситуації, коли це доцільно, слід враховувати винятки та повертати значення помилки абоненту, що потребує рефакторингу у реалізацію Catch → Rethrow.


Так, я вже знаю, і це не викликає сумніву, що я повинен кидати винятки в тому місці, де походить помилка. Але чому я не повинен спіймати NPE і замість цього дозволити йому піднятися вгору по Стек Я завжди би спіймав NPE і перетворив би його на значущий виняток. Я також не бачу жодної переваги, чому я повинен перекинути DAO-виняток на рівень сервісу чи інтерфейсу користувача. Я завжди вловлював би його на рівні обслуговування і перетворював би його на виняток із додаткової детальної інформації, чому викликати службу не вдалося.
shylynx

8
@shylynx Зробити виняток, а потім повторно скинути більш значущий виняток - це добре зробити. Що ви не повинні робити, це ловити виняток занадто рано, а потім не скидати його. Помилка, про яку висловлюється попередження, занадто рано ловить виняток, а потім намагається обробити його на неправильному шарі в коді.
Саймон Б

Зростання контексту очевидним на момент отримання винятку полегшує життя розробникам вашої команди. NPE вимагає подальшого розслідування, щоб зрозуміти проблему
Майкл Шоу

4
@shylynx Можна задати питання: "Чому у вас є код у вашому коді, який може викинути NullPointerException? Чому б не перевірити nullі не кинути виняток (можливо IllegalArgumentException) раніше, щоб абонент точно знав, куди nullпотрапив поганий ?" Я вважаю, що це було б те, що запропонувала б частина «прискореної передачі».
jpmc26

2
@jpmc Я взяв NPE лише як приклад, щоб наголосити на проблемах навколо шарів та винятках. Я також міг би замінити його на IllegalArgumentException.
shylynx

56

Ви хочете якнайшвидше кинути виняток, оскільки це полегшує пошук причини. Наприклад, розглянемо метод, який може провалитися з певними аргументами. Якщо ви перевірите аргументи і не вдається на самому початку методу, ви одразу знаєте, що помилка є в коді виклику. Якщо ви зачекаєте, поки аргументи будуть потрібні, перш ніж вийти з ладу, вам доведеться стежити за виконанням і з'ясувати, чи є помилка у викликовому коді (неправильний аргумент) чи метод має помилку. Чим раніше ви кидаєте виняток, тим ближче до його основної причини і простіше зрозуміти, де все пішло не так.

Причина, що винятки обробляються на більш високих рівнях, полягає в тому, що нижчі рівні не знають, який відповідний спосіб дії для усунення помилки. Насправді, може бути кілька відповідних способів вирішити одну і ту ж помилку залежно від того, що є кодом виклику. Наприклад, відкрийте файл. Якщо ви намагаєтесь відкрити файл конфігурації, а його немає, ігнорування винятку та продовження конфігурації за замовчуванням може бути відповідним відповіддю. Якщо ви відкриваєте приватний файл, який життєво важливий для виконання програми, і він якось відсутній, ваш єдиний варіант, ймовірно, закрити програму.

Захоплення винятків у правильних типах є суто ортогональним питанням.


1
+1 для чіткого пояснення, чому різні рівні мають значення. Відмінний приклад помилки файлової системи.
Хуан Карлос Кото

24

Інші досить добре підсумували, чому кидати рано . Дозвольте зосередитися на тому, чому натомість піймати пізню частину, для якої я не бачив задовольняючого пояснення свого смаку.

ЧОМУ ЧОМУ ВИКОНАННЯ?

Здається, існує велика плутанина навколо того, чому в першу чергу існують винятки. Дозвольте мені поділитися тут великою таємницею: причина винятків та поводження з винятками - ... РЕЗУЛЬТАТ .

Ви бачили такий код:

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. Щасливого кодування!


Ви впевнені, що код, що постачає інтерфейс, дізнається більше про те, що може піти не так, ніж код, який використовує інтерфейс, але припустимо, що метод використовує два ресурси одного типу інтерфейсу, і помилки потрібно обробляти по-різному? Або якщо один із цих ресурсів внутрішньо, як деталь реалізації, не відомий його творцю, використовує інші вкладені ресурси того ж типу? Запропонуйте діловому шару WrappedFirstResourceExceptionабо WrappedSecondResourceExceptionвимагаючи проводки для прогляду всередині цього винятку, щоб побачити першопричину проблеми ...
supercat

... може бути неприємним, але, здавалося б, краще, ніж припускати, що будь-який FailingInputResourceвиняток буде результатом операції з in1. Насправді, я думаю, що в багатьох випадках правильним підходом було б, щоб шар проводки передав об'єкт, що обробляє винятки, і щоб бізнес-рівень включав, catchякий потім викликає handleExceptionметод цього об'єкта . Цей метод може перезавантажити або надати дані за замовчуванням або встановити підказку "Скасувати / Повторити / Помилка" і дати оператору можливість вирішити, що робити і т.д. залежно від того, що вимагає програма.
supercat

@supercat Я розумію, що ти кажеш. Я б сказав, що конкретна реалізація ресурсів відповідає за те, щоб знати винятки, які вона може спричинити. Він не повинен вказувати все (існує так звана невизначена поведінка ), але він повинен переконатись у відсутності двозначності. Крім того, повинні бути задокументовані неперевірені винятки з часу виконання. Якщо це суперечить документації, то це помилка. Якщо очікується, що код абонента зробить щось розумне щодо винятку, найнижчим мінімумом є те, що ресурс обертає їх у деякий UnrecoverableInternalException, подібний коду помилки HTTP 500.
Даніель Дінніс

@supercat Про вашу пропозицію щодо налаштованих обробників помилок: саме! У моєму останньому прикладі логіка обробки помилок жорстко кодується, викликаючи статичний doMyBusinessметод. Це було заради стислості, і цілком можливо зробити його більш динамічним. Такий Handlerклас буде створений з деякими вхідними / вихідними ресурсами та матиме handleметод, який отримує клас, що реалізує ReusableBusinessLogicInterface. Потім можна комбінувати / налаштувати для використання різних реалізацій оброблювача, ресурсів та бізнес-логіки в проводковому шарі десь над ними.
Даніель Дінніс

10

Щоб правильно відповісти на це питання, давайте зробимо крок назад і задамо ще більш фундаментальне питання.

Чому ми маємо винятки в першу чергу?

Ми викидаємо винятки, щоб повідомити абоненту нашого методу, що ми не могли робити те, про що нас просили. Тип винятку пояснює, чому ми не могли робити те, що хотіли зробити.

Давайте подивимось на якийсь код:

double MethodA()
{
    return PropertyA - PropertyB.NestedProperty;
}

Цей код, очевидно, може кинути нульовий посилання на виняток, якщо він PropertyBє null. Є дві речі, які ми могли б зробити в цьому випадку, щоб «виправити» дану ситуацію. Ми могли б:

  • Автоматично створювати PropertyB, якщо у нас його немає; або
  • Нехай виняток піднімається до виклику методу.

Створення PropertyB тут може бути дуже небезпечним. З якої причини цей метод має створити PropertyB? Звичайно, це порушило б принцип єдиної відповідальності. Ймовірно, якщо PropertyB тут не існує, це вказує на те, що щось пішло не так. Метод викликається на частково побудованому об'єкті або PropertyB встановлено на нуль неправильно. Створюючи PropertyB тут, ми могли б приховати набагато більшу помилку, яка могла б нас укусити згодом, наприклад, помилка, яка спричиняє пошкодження даних.

Якщо замість цього ми пустимо міхур нульової посилання, ми повідомляємо розробнику, який викликав цей метод, як тільки ми зможемо, що щось пішло не так. Важлива умова виклику цього методу була пропущена.

Тому насправді ми кидаємо рано, бо це набагато краще розділяє наші проблеми. Як тільки виникла помилка, ми повідомляємо про це розробникам вище.

Чому ми «спіймаємо пізно» - це вже інша історія. Ми насправді не хочемо спіймати пізно, ми дуже хочемо зловити якнайшвидше, оскільки знаємо, як правильно вирішити проблему. Деякий час це пізніше буде п’ятнадцять шарів абстракції, а деякий час це буде на місці створення.

Річ у тім, що ми хочемо зафіксувати виняток на шарі абстракції, який дозволяє нам обробляти виняток у тому місці, де у нас є вся інформація, необхідна для належного поводження з винятком.


Я думаю, ви використовуєте розробників вище за течією в неправильному розумінні. Крім того, ви сказали, що це порушує принцип єдиної відповідальності, але насправді багато хто з ініціалізації запитів і кешування
цін

У вашому наведеному прикладі, як щодо перевірки на null перед операцією віднімання, як цеif(PropertyB == null) return 0;
user1451111

1
Чи можете ви також детальніше розповісти про свій останній абзац, зокрема, що ви маєте на увазі під « шаром абстракції ».
user1451111

Якщо ми робимо якусь роботу ІО, то шар абстрагування для вилучення виключення IO був би там, де ми виконуємо роботу. У цей момент у нас є вся інформація, яку нам потрібно вирішити, чи хочемо ми повторити або перекинути в поле поле повідомлення або створити об’єкт за допомогою набору за замовчуванням.
Стівен

"У вашому наведеному прикладі, як щодо перевірки на null перед операцією віднімання, наприклад, якщо (PropertyB == null) повернути 0;" Гидота. Це означало б метод виклику, що у мене є вагомі речі, які слід відняти до та з них. Звичайно, це контекстуально, але було б поганою практикою робити перевірку помилок у більшості випадків.
Стівен

6

Киньте, як тільки побачите щось, на що варто кинути, щоб не ставити предмети в недійсний стан. Це означає, що якщо нульовий покажчик був переданий, ви перевіряєте його на ранніх термінах і кидаєте NPE, перш ніж у нього з’явиться шанс спуститися до низького рівня.

Ловіться, як тільки ви дізнаєтесь, що робити, щоб виправити помилку (зазвичай це не місце, де ви кидаєте, інакше ви можете просто скористатись if-else), якщо недійсний параметр був переданий, то шар, який надав параметр, повинен мати справу з наслідками .


1
Ви писали: Киньте незабаром, ... Ловіться скоро ...! Чому? Це абсолютно протилежний підхід на відміну від "рано кидати, пізно піймати".
shylynx

1
@shylynx Я не знаю, звідки походить "рано кидайте, спіймайте пізно", але її значення сумнівне. Що, власне, означає ловити "пізно"? Де є сенс зловити виняток (якщо він взагалі є), залежить від проблеми. Єдине, що зрозуміло - це ви хочете виявити проблеми (і кинути) якомога раніше.
Довал

2
Я припускаю, що "ловить пізно" призначений для протиставлення практиці лову, перш ніж ви зможете знати, що робити, щоб виправити помилку - наприклад, іноді ви бачите функції, які вловлюють все просто, щоб вони могли надрукувати повідомлення про помилку, а потім повторно скинути виняток.

@Hurkyl: Проблема з "ловом пізно" полягає в тому, що якщо виняток прорізається через шари, які нічого про нього не знають, коду може бути важко зробити щось із ситуації, щоб знати, що справи дійсно такі очікуваний. Як простий приклад, припустимо, якщо аналізатору файлу документа користувача потрібно завантажити CODEC з диска і під час читання цього виникає помилка диска, код, який викликає аналізатор, може діяти неадекватно, якщо він вважає, що сталася помилка диска під час читання користувача документ.
supercat

4

Дійсне правило бізнесу: "Якщо програмне забезпечення нижчого рівня не вдається обчислити значення, то ..."

Це можна виразити лише на вищому рівні, інакше програмне забезпечення нижчого рівня намагається змінити свою поведінку виходячи із власної коректності, яка лише закінчиться у вузлі.


2

Перш за все, винятки становлять виняткові ситуації. У вашому прикладі жодна цифра не може бути обчислена, якщо необроблених даних немає, тому що їх неможливо було завантажити.

З мого досвіду, добре застосовувати абстрактні винятки під час прогулянки стеком. Як правило, точки, де ви хочете це зробити, це кожен раз, коли виняток перетинає межу між двома шарами.

Якщо є помилка збору ваших необроблених даних у рівні даних, киньте виняток, щоб повідомити того, хто запитав ці дані. Не намагайтеся вирішити цю проблему тут. Складність коду обробки може бути дуже високою. Також рівень даних відповідає лише за запит даних, а не за обробку помилок, які виникають під час цього. Це те, що означає «кинути рано» .

У вашому прикладі ловлячим шаром є службовий рівень. Сама служба являє собою новий рівень, розташований над рівнем доступу до даних. Тож ви хочете там зловити виняток. Можливо, ваша служба має певну інфраструктуру відмови та намагається запитати дані з іншого сховища. Якщо це також не вдається, оберніть виняток всередині того, що розуміє абонент служби (якщо це веб-служба, це може бути помилка SOAP). Встановіть початковий виняток як внутрішній виняток, щоб пізніші шари могли записувати саме те, що пішло не так.

Помилка сервісу може бути схоплена шаром, що викликає службу (наприклад, інтерфейс користувача). І це те, що означало "пізно спіймати" . Якщо ви не в змозі обробити виняток у нижньому шарі, повторно закиньте його. Якщо найвищий шар не може впоратися з винятком, обробіть його! Сюди може входити реєстрація або представлення її.

Причина, чому ви повинні повторно скинути винятки (як описано вище, загортаючи їх у більш загальні exepep), полягає в тому, що користувач, швидше за все, не може зрозуміти, що сталася помилка, оскільки, наприклад, вказівник вказав на недійсну пам'ять. І йому все одно. Його хвилює лише те, що цифру не вдалося вирахувати службою, і це інформація, яка повинна йому відображатися.

Якщо ви підете далі, ви можете (в ідеальному світі) повністю залишити try/ catchкод з інтерфейсу користувача. Натомість використовуйте глобальний обробник винятків, який здатний зрозуміти винятки, які, можливо, кинуті нижчими шарами, записує їх у якийсь журнал і вкладає їх у об'єкти помилок, які містять змістовну (і, можливо, локалізовану) інформацію про помилку. Ці об'єкти можуть бути легко представлені користувачеві в будь-якій бажаній формі (вікна повідомлень, сповіщення, тости повідомлень тощо).


1

Як правило, рано викидання винятків є хорошою практикою, оскільки ви не хочете, щоб порушені контракти проходили через код далі, ніж це потрібно. Наприклад, якщо ви очікуєте, що певний функціональний параметр буде цілим числом, вам слід застосувати це обмеження в точці виклику функції, а не чекати, поки ця змінна не буде використана де-небудь ще в стеці коду.

Ловити пізно, я не можу коментувати, тому що у мене є свої правила, і це змінюється від проекту до проекту. Я намагаюся зробити лише те, що розділити винятки на дві групи. Один призначений лише для внутрішнього використання, а другий - лише для зовнішнього використання. Внутрішні винятки вловлюються та обробляються моїм власним кодом, а зовнішній виняток призначений для оброблення будь-яким кодом, який викликає мене. Це, в основному, форма лову речей пізніше, але не зовсім тому, що вона пропонує мені гнучкість відхилятися від правила, коли це необхідно у внутрішньому коді.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.