Конвенції про винятки або коди помилок


118

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

Які правила ви використовуєте, щоб вирішити, чи викидаєте винятки чи повертаєте коди помилок для повідомлення про помилки?

Відповіді:


81

У матеріалах високого рівня, винятки; в матеріалах низького рівня, кодах помилок.

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

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

І Реймонд Чен, і Джоел зробили кілька красномовних аргументів проти використання винятків для всього.


5
+1 для вказівки, що контекст має щось спільне із стратегією обробки помилок.
alx9r

2
@ Том, хороші бали, але винятки гарантовано попадуться. Як переконатися , що коди помилок спіймані й не проігноровані з - за помилки?
Pacerier

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

3
C ++ 17 вводить nodiscardатрибут, який видасть попередження компілятора, якщо значення, що повертається, функція не зберігається. Допомагає трохи схопити забуту перевірку коду помилки. Приклад: godbolt.org/g/6i6E0B
Zitrax

5
@ Коментар Esailija саме тут. Аргумент проти винятків тут приймає гіпотетичний API на основі помилок на коді, де всі коди помилок задокументовані, а гіпотетичний програміст, який читає документацію, правильно ідентифікує всі випадки помилок, які логічно можливі в її застосуванні, і пише код для обробки кожного з них , потім порівнює цей сценарій з API, заснованим на гіпотетичних винятках, та програмістом, де чомусь один із цих кроків піде не так ... навіть якщо однаково легко (можливо, простіше ) отримати всі ці кроки прямо в API, заснованому на винятках.
Марк Амері

62

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

З іншого боку, коди помилок більш легкі, ніж винятки, але їх складніше підтримувати. Помилка перевірки може бути ненавмисно опущена. Коди помилок важче підтримувати, оскільки вам доведеться вести каталог із усіма кодами помилок, а потім увімкнути результат, щоб побачити, яку помилку було викинуто. Тут можуть допомогти діапазони помилок, тому що якщо єдине, що нас цікавить, - чи ми знаходимось у помилці чи ні, перевірити її простіше (наприклад, код помилки HRESULT більший або дорівнює 0 - це успіх і менше нуля - відмова). Їх можна ненавмисно опустити, оскільки не існує програмного примусу, який розробник перевірятиме на наявність кодів помилок. З іншого боку, ви не можете ігнорувати винятки.

Підводячи підсумок, я віддаю перевагу виняткам над кодами помилок майже у всіх ситуаціях.


4
"Коди помилок легші, ніж винятки", залежить від того, що ви вимірюєте та як вимірюєте. Досить легко придумати тести, які показують API, що базується на винятках, може бути набагато швидшим.
Mooing Duck

1
@smink, Добрі моменти, але як ми вирішуємо накладні винятки? Коди помилок - це не просто легка вага, вони в основному невагомі ; винятки - це не лише середня вага, це важкі предмети, що містять інформацію про стеки та різні речі, які ми все одно не використовуємо.
Pacerier

3
@Pacerier: Ви розглядаєте лише тести, де викидаються винятки. Ви на 100% праві, що викид винятків C ++ значно повільніше, ніж повернення коду помилки. Руки вниз, ніяких дебатів. Де ми дійсно відрізняються, це інша 99,999% коду. За винятком, нам не потрібно перевіряти повернення коду помилки між кожним оператором, роблячи цей код на 1–50% швидшим (чи ні, залежно від компілятора). Що означає, що повний код може швидше або повільніше залежати повністю від того, як пишеться код і як часто викидаються винятки.
Mooing Duck

1
@Pacerier: За допомогою цього штучного тесту я щойно писав, код на основі винятку такий же швидкий, як коди помилок у MSVC та Clang, хоча не GCC: coliru.stacked-crooked.com/a/e81694e5c508945b (таймінги внизу). Здається, що використовувані я прапори GCC створили незвично повільні винятки порівняно з іншими компіляторами. Я упереджений, тому, будь ласка, критикуйте мій тест, і не соромтеся спробувати інший варіант.
Mooing Duck

4
@Mooing Duck. Якщо ви перевіряли одночасно і коди помилок, і винятки, у вас, можливо, були включені винятки. Якщо це так, ваші результати дозволяють припустити, що використання кодів помилок з обробкою винятків не є повільнішим, ніж використання лише виключень. Тести коду помилки повинні бути виконані з виключеннями виключень, щоб отримати значущі результати.
Mika Haarahiltunen

24

Я віддаю перевагу виняткам, тому що

  • вони перетинають потік логіки
  • їм вигідна ієрархія класів, яка дає більше можливостей / функціональності
  • при правильному використанні може представляти широкий спектр помилок (наприклад, InvalidMethodCallException - це також LogicException, оскільки обидва трапляються, коли у вашому коді є помилка, яку слід виявити перед виконанням), і
  • їх можна використовувати для посилення помилки (тобто визначення класу FileReadException може потім містити код, щоб перевірити, чи існує файл, чи заблокований тощо)

2
Ваш четвертий пункт не є справедливим: стан помилки при перетворенні в об’єкт, також може містити код, щоб перевірити, чи існує файл, чи заблокований тощо. Це просто варіант stackoverflow.com/a/3157182/632951
Pacerier

1
"вони перетинають потік логіки". Винятки мають більш-менш наслідкиgoto
петерхаула

22

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


17

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

Ось кілька статей, які обговорюють, порівнюють та протиставляють дві методики:

Є кілька хороших посилань у тих, які можуть дати вам подальше читання.


16

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

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

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

Employee EmpOfMonth = GetEmployeeOfTheMonth();

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

Employee EmpOfMonth; 
if (getEmployeeOfTheMonth(ref EmpOfMonth) == ERROR)
    // code to Handle the error here

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


Чудова відповідь! Вийшов з коробки рішення вчасно!
Крістіан Е.

11

Раніше я приєднався до табору кодів помилок (зробив занадто багато програмування на С). Але тепер я побачив світло.

Так, винятки трохи навантажують систему. Але вони спрощують код, зменшуючи кількість помилок (та WTF).

Тому використовуйте винятки, але використовуйте їх розумно. І вони стануть твоїм другом.

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


1
yap C все ж залишає у нас кілька звичок;)
Хорхе Феррейра

11

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

Коли відбувається виняток, додаток більше не йде за "нормальним" шляхом виконання. Перша причина, чому це так важливо, полягає в тому, що, якщо автор коду не піде добре і по-справжньому не зійде з дороги погано, програма зупиниться і не продовжить робити непередбачувані речі. Якщо код помилки не перевіряється, а відповідні дії не приймаються у відповідь на неправильний код помилки, програма продовжуватиме робити те, що робить, і хто знатиме, що буде результатом цієї дії. Існує багато ситуацій, коли програма, яка робить "все, що завгодно", може стати дуже дорогою. Розгляньте програму, яка отримує інформацію про результати діяльності для різних фінансових інструментів, яку продає компанія, і передає цю інформацію брокерам / оптовим торговцям. Якщо щось піде не так, і програма продовжує працювати, він може надсилати помилкові дані про діяльність брокерам та оптовим торговцям. Я не знаю ні про кого іншого, але я не хочу бути тим, хто сидить у офісі VP, пояснюючи, чому мій код спричинив, що компанія отримала 7-цифрові нормативні штрафи. Подавати повідомлення про помилку клієнтам, як правило, бажано надсилати неправильні дані, які можуть виглядати «справжніми», і останню ситуацію набагато простіше зіткнутися з набагато менш агресивним підходом, як коди помилок.

Друга причина, чому мені подобаються винятки та їхнє порушення нормального виконання, полягає в тому, що це робить набагато набагато простіше тримати логіку «нормальних речей» окремо від логіки «щось пішло не так». Для мене це:

try {
    // Normal things are happening logic
catch (// A problem) {
    // Something went wrong logic
}

... переважніше для цього:

// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}
// Some normal stuff logic
if (errorCode means error) {
    // Some stuff went wrong logic
}

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


8

Ви повинні використовувати обидва. Річ у тому, щоб вирішити, коли використовувати кожен .

Є декілька сценаріїв, коли винятки є очевидним вибором :

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

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

  3. У деяких ситуаціях у вас є послідовність команд, і якщо будь-яка з них виходить з ладу, вам слід очистити / видалити ресурси (*), незалежно від того, це ситуація, яку не можна відновити (яку слід кинути), або ситуацію, що підлягає відновленню (у такому випадку ви можете обробляти локально або в коді абонента, але винятки не потрібні). Очевидно, що набагато простіше поставити всі ці команди в одну спробу, замість тестування кодів помилок після кожного методу та очищення / розпорядження в остаточному блоці. Зауважте, що якщо ви хочете, щоб помилка зростала (що, мабуть, те, що ви хочете), вам навіть не потрібно її вловлювати - ви просто використовуєте нарешті для очищення / знешкодження - вам слід використовувати лише ловлю / повторне використання, якщо ви хочете додати контекстну інформацію (див. пул.2).

    Одним із прикладів може бути послідовність операторів SQL всередині блоку транзакцій. Знову ж таки, це також "непридатна" ситуація, навіть якщо ви вирішите зловити її рано (лікуйте її місцево, а не киплячи до верху), це все ще фатальна ситуація, коли найкращим результатом є переривання всього або принаймні переривання великого частина процесу.
    (*) Це як те, on error gotoщо ми використовували в старому Visual Basic

  4. У конструкторах можна кидати лише винятки.

Сказавши це, у всіх інших ситуаціях, коли ви повертаєте деяку інформацію, за якою абонент МОЖЕ / ВІДКЛАДИТИ вжити певних дій , використовуючи коди повернення, мабуть, краща альтернатива. Сюди входять усі очікувані "помилки" , тому що, ймовірно, з ними повинен працювати негайний абонент, і навряд чи потрібно буде переповнювати занадто багато рівнів в стеку.

Звичайно, завжди можна трактувати очікувані помилки як винятки, а потім вловлювати відразу на один рівень вище, а також можна охопити кожен рядок коду у спробі лову та вжити заходів для кожної можливої ​​помилки. IMO, це поганий дизайн не тільки тому, що він набагато більш багатослівний, але спеціально тому, що можливі винятки, які можуть бути кинуті, не очевидні без читання вихідного коду - і винятки можуть бути викинуті з будь-якого глибокого методу, створюючи невидимі готи . Вони порушують структуру коду, створюючи кілька невидимих ​​точок виходу, які ускладнюють читання та перевірку коду. Іншими словами, ви ніколи не повинні використовувати винятки як контроль потоку, тому що іншим було б важко зрозуміти та підтримати. Це може отримати навіть важко зрозуміти всі можливі потоки коду для тестування.
Знову ж таки: для правильної очистки / утилізації ви можете використовувати спробу, остаточно не вловлюючи нічого .

Найпопулярніша критика щодо повернення кодів полягає в тому, що "хтось міг ігнорувати коди помилок, але в тому ж сенсі хтось також може проковтнути винятки. Погане поводження з винятками легко в обох методах. Але написання хорошої програми на основі коду помилок все ще набагато простіше ніж написання програми, що базується на винятках . І якщо хтось із будь-яких причин вирішить ігнорувати всі помилки (старі on error resume next), ви можете легко зробити це з кодами повернення, і ви не можете цього зробити без великої кількості спроб-котів.

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

Рішення між винятками та кодами помилок - це сіра область. Можливо навіть, що вам потрібно отримати код помилки з якогось бізнес-методу багаторазового використання, а потім ви вирішите перетворити його на виняток (можливо, додавши інформацію) і дозвольте йому перетворюватися на пульс. Але помилкою в дизайні вважати, що ВСІ помилки слід викидати як винятки.

Підсумовуючи це:

  • Мені подобається використовувати винятки, коли у мене виникає несподівана ситуація, в якій мало що робити, і зазвичай ми хочемо відмінити великий блок коду або навіть всю операцію чи програму. Це як старий "на помилку goto".

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

Ця різниця між винятками та кодами помилок є одним із принципів проектування мови GO, яка використовує "паніку" для фатальних несподіваних ситуацій, а звичайні очікувані ситуації повертаються як помилки.

Що стосується GO, він також дозволяє отримати декілька значень повернення , що дуже допомагає при використанні кодів повернення, оскільки ви можете одночасно повернути помилку та щось інше. На C # / Java ми можемо досягти цього за допомогою вихідних параметрів, Tuples або (мого улюбленого) Generics, які в поєднанні з перерахунками можуть надавати чіткий код помилок абоненту:

public MethodResult<CreateOrderResultCodeEnum, Order> CreateOrder(CreateOrderOptions options)
{
    ....
    return MethodResult<CreateOrderResultCodeEnum>.CreateError(CreateOrderResultCodeEnum.NO_DELIVERY_AVAILABLE, "There is no delivery service in your area");

    ...
    return MethodResult<CreateOrderResultCodeEnum>.CreateSuccess(CreateOrderResultCodeEnum.SUCCESS, order);
}

var result = CreateOrder(options);
if (result.ResultCode == CreateOrderResultCodeEnum.OUT_OF_STOCK)
    // do something
else if (result.ResultCode == CreateOrderResultCodeEnum.SUCCESS)
    order = result.Entity; // etc...

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


4

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

У всіх інших випадках, ймовірно, винятком є ​​шлях.


4

Я, можливо, сиджу тут на паркані, але ...

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

У Python використання винятків є звичайною практикою, і я дуже радий визначити власні винятки. У С у вас зовсім немає винятків.

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

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


4

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

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


3

Підписи методів повинні повідомляти вам, що робить метод. Щось на зразок long errorCode = getErrorCode (); може бути добре, але довга errorCode = fetchRecord (); заплутано.


3

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

Я використовую для визначення декількох типів винятків (наприклад: DataValidationException або ProcessInterruptExcepion) і всередині кожного винятку визначаю більш детальний опис кожної проблеми.

Простий приклад на Java:

public class DataValidationException extends Exception {


    private DataValidation error;

    /**
     * 
     */
    DataValidationException(DataValidation dataValidation) {
        super();
        this.error = dataValidation;
    }


}

enum DataValidation{

    TOO_SMALL(1,"The input is too small"),

    TOO_LARGE(2,"The input is too large");


    private DataValidation(int code, String input) {
        this.input = input;
        this.code = code;
    }

    private String input;

    private int code;

}

Таким чином я використовую Винятки для визначення помилок категорії та код помилок для визначення більш детальної інформації про проблему.


2
ем ... throw new DataValidationException("The input is too small")? Однією з переваг винятків є надання детальної інформації.
Єва

2

Винятки становлять виняткові обставини, тобто коли вони не входять до нормального потоку коду.

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

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

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

Але перехід в іншому напрямку за допомогою винятків дозволяє будувати ще більш високі абстракції для вашої помилки, що може зробити ваш код ще більш виразним та природним. Я настійно рекомендую прочитати цю чудову, але недооцінену статтю експерта з C ++ Андрія Олександреску на тему, яку він називає, "Виконання": http://www.ddj.com/cpp/184403864 . Незважаючи на те, що це стаття C ++, принципи, як правило, застосовуються, і я переклав концепцію примусового виконання на C # досить успішно.


2

По- перше, я згоден з Тома відповідь , що для високого рівня винятків використовують матеріал, і для кодів помилок використовуйте матеріал низького рівня, поки це не сервіс - орієнтованої архітектури (SOA).

У SOA, де методи можуть бути викликані на різних машинах, винятки можуть не передаватися по дроту, натомість ми використовуємо відповіді на успіх / збій із такою структурою, як нижче (C #):

public class ServiceResponse
{
    public bool IsSuccess => string.IsNullOrEmpty(this.ErrorMessage);

    public string ErrorMessage { get; set; }
}

public class ServiceResponse<TResult> : ServiceResponse
{
    public TResult Result { get; set; }
}

І використовуйте так:

public async Task<ServiceResponse<string>> GetUserName(Guid userId)
{
    var response = await this.GetUser(userId);
    if (!response.IsSuccess) return new ServiceResponse<string>
    {
        ErrorMessage = $"Failed to get user."
    };
    return new ServiceResponse<string>
    {
        Result = user.Name
    };
}

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


1

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

Повернення недійсних покажчиків, які можуть бути відкинуті (наприклад, викликаючи NullPointerException в Java), неприпустимо.

Використання декількох різних числових кодів помилок (-1, -2) в якості повернених значень для однієї і тієї ж функції зазвичай є поганим стилем, оскільки клієнти можуть робити перевірку "== -1" замість "<0".

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

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

У будь-якому випадку, коли можливе більше одного тривіального типу помилок, вихідний код ніколи не повинен використовувати числовий літерал для повернення коду помилки або для його обробки (повернення -7, якщо x == -7 ...), але завжди названа константа (повернути NO_SUCH_FOO, якщо x == NO_SUCH_FOO).


1

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

Наприклад, ви вирішили використовувати лише винятки. Але як тільки ви вирішите використовувати обробку подій async. В таких ситуаціях погано використовувати винятки для обробки помилок. Але використовувати коди помилок скрізь у застосуванні - нудно.

Тож моя думка, що нормально використовувати одночасно і винятки, і коди помилок.


0

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


0

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

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

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


0

Моє загальне правило:

  • У функції може з’явитися лише одна помилка: використовувати код помилки (як параметр функції)
  • Можливо, з’явиться більше однієї конкретної помилки: виняток кинути

-1

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


3
Гм, ні. Дивіться парадигму win32 GetLastError (). Я не захищаю це, просто заявляю, що ви неправі.
Тім

4
Насправді існує багато інших способів зробити це. Інший спосіб - повернути об’єкт, який містить код помилки та реальне значення повернення. Ще один спосіб - це зробити проходження посилання.
Pacerier
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.