Як створити винятки


11

Я боюся з дуже простим питанням:

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

Я думаю про наступну стратегію:

1) Що йде не так?

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

2) Хто запускає запит?

  • Клієнтська програма
  • Ще одне серверне додаток

3) Подання повідомлень: оскільки ми маємо справу з серверною програмою, справа в тому, щоб приймати та надсилати повідомлення. Що робити, якщо відправлення повідомлення піде не так?

Таким чином, ми можемо отримати такі типи винятків:

  • ServerNotAllowedException
  • ClientNotAllowedException
  • ServerParameterException
  • ClientParameterException
  • InternalException (якщо сервер не знає, звідки надходить запит)
    • ServerInternalException
    • ClientInternalException
  • MessageHandlingException

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

Спасибі заздалегідь


5
Ви не згадали, чого хочете досягти зі своєю ієрархією класів винятків (і це зовсім не очевидно). Значущий лісозаготівлю? Дозволити клієнтам розумно реагувати на різні винятки? Або що?
Ральф Клеберхофф

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

1
Щодо важливості, я б припустив: softwareengineering.stackexchange.com/questions/278949/…
Martin Ba

1
Я ніколи не розумів бажання хотіти використовувати стільки різних типів виключень. Зазвичай для більшості catchблоків, які я використовую, я не маю набагато більшого використання за винятком, ніж те, яке повідомлення про помилку містить. У мене насправді немає нічого іншого, що я можу зробити за виняток, пов'язаний з тим, що не читати файл, як один не в змозі виділити пам'ять під час процесу його читання, тому я прагну просто зафіксувати std::exceptionповідомлення про помилку, яке воно містить, можливо, прикрасити його "Failed to open file: %s", ex.what()до буфера стека перед друком.

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

Відповіді:


5

Загальні зауваження

(трохи упереджене думкою)

Зазвичай я б не звертався до детальної ієрархії винятків.

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

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

Третій аспект - це реакція вашого абонента. Що може зробити ваш абонент, коли отримає виняток? Тут може бути сенс мати різні класи винятків, тому абонент може вирішити, чи повторити той самий виклик, використовувати інше рішення (наприклад, замість цього використовувати резервне джерело) або відмовитися.

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

Важливим аспектом для ведення журналів (і для повідомлень про помилки користувачів) є можливість вносити зміни до винятку з контекстною інформацією, вловлюючи її на якомусь рівні, додаючи якусь інформацію про контекст, наприклад параметри методу, та повторно перекидаючи її.

Ваша ієрархія

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

Обробка повідомлень : це не інший аспект, а лише додаткові випадки для "Що відбувається не так?".

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

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


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

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

2

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

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

  • Визначте MyClientServerAppExceptionбазовий клас, щоб охопити помилки, унікальні в контексті вашої програми. Ніколи не кидайте екземпляр базового класу. Неоднозначна відповідь на помилку - НАЙБІЛЬШЕ ЩЕ . Якщо є "внутрішня помилка", то поясніть, що це за помилка.

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

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

  • Що стосується попереднього пункту, пам’ятайте, що те, що ви використовуєте винятки для відповіді на помилки, це не означає, що вам потрібно використовувати лише винятки для станів помилок. Майте на увазі, що викидання винятків коштують зазвичай, а вартість продуктивності може варіюватися від однієї мови до іншої. Для деяких мов вартість вище, залежно від глибини стека викликів, тому, якщо помилка є глибокою в стеці викликів, перевірте, чи не можна використовувати прості примітивні типи (цілі коди помилок або булеві прапори) для натискання помилка створює резервну копію стека, тому її можна перенести ближче до виклику абонента.

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

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


2
За винятком вашої другої та останньої кулі (про вартість винятків), відповідь хороша. Ця таблиця щодо вартості винятків вводить в оману, оскільки у всіх поширених способах впровадження виключень на низькому рівні вартість передачі виключення повністю не залежить від глибини стека викликів. Альтернативні методи повідомлення про помилки можуть бути кращими, якщо ви знаєте, що помилку буде обробляти негайний абонент, але не отримувати кілька функцій зі стека викликів перед тим, як викинути виняток.
Барт ван Інген Шенау

@BartvanIngenSchenau: Я намагався не прив’язувати це до конкретної мови. У деяких мовах (наприклад, Java ) глибина стека викликів впливає на вартість інстанції. Я відредагую, щоб відобразити, що це не так нарізано і висушено.
Марк Беннінгфілд

0

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

Це дає змогу ловити будь-які винятки базового DuckTypeExceptionкласу для обробки. Звідси ваші винятки повинні відповідати описовим іменам, які можуть краще висвітлити тип проблеми. Наприклад, "DatabaseConnectionException".

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

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

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

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

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


2
Зауважте, що питання позначено тегом "C ++".
Мартін Ба

0

Спробуйте спростити це.

Перше, що допоможе вам подумати в іншій стратегії, це: спіймане багато винятків дуже схоже на використання перевірених винятків з Java (вибачте, я не розробник C ++). Це не годиться з багатьох причин, тому я завжди намагаюся їх не використовувати, і ваша стратегія виключення з ієрархії мені пам’ятає дуже багато цього.

Тому я рекомендую вам ще одну і гнучку стратегію: використовуйте неперевірені винятки та помилки коду.

Наприклад, дивіться цей код Java:

public class SystemErrorCode implements ErrorCode {

    INVALID_NAME(101),
    ORDER_NOT_FOUND(102),
    PARAMETER_NOT_FOUND(103),
    VALUE_TOO_SHORT(104);

    private final int number;

    private ErrorCode(int number) {
        this.number = number;
    }

    @Override
    public int getNumber() {
        return number;
    }
}

І ваш унікальний виняток:

public class SystemException extends RuntimeException {

    private ErrorCode errorCode;

    public SystemException(ErrorCode errorCode) {
        this.errorCode = errorCode;
    }

}

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

Оскільки вам потрібно розділити різні винятки серед програм «клієнт» та «інший сервер», у вас може бути кілька класів кодів помилок, що реалізують інтерфейс ErrorCode.


2
Зауважте, що питання позначено тегом "C ++".
Sjoerd

Я редагував, щоб адаптувати ідею.
Дерік

0

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

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

Винятки повинні бути в контексті викликаної функції. Наприклад: функція, яка розв’язує квадратичні рівняння . Він повинен обробляти два винятки: поділ_by_zero та square_root_of_negative_number. Але ці винятки не мають сенсу для функцій, що називають це вирішення квадратичного рівняння. Вони трапляються через метод, який використовується для розв’язання рівнянь, і просто їх повторне викриття розкриває внутрішню частину і порушує інкапсуляцію. Натомість поділ_by_zero слід перекреслити як not_quadratic та square_root_of_negative_number та no_real_roots.

Немає потреби в ієрархії винятків. Перелік винятків (який їх ідентифікує), кинутий функцією, є достатнім, оскільки функція виклику повинна керувати ними. Дозволити їм обробляти дерево викликів є позаконтекстним (необмеженим) goto.

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