Невже ловити загальні винятки насправді погано?


57

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

CA1031: Не вловлюйте загальні типи виключень

Я розумію обґрунтування цього правила. Але на практиці, якщо я хочу вжити однакових дій незалежно від кинутого винятку, чому я б поводився з кожним конкретно? Крім того, якщо я обробляю конкретні винятки, що робити, якщо код, який я викликаю, змінити, щоб у майбутньому викинути новий виняток? Тепер я повинен змінити свій код, щоб обробити цей новий виняток. Тоді як, якщо я просто спіймав Exceptionкод, його не потрібно змінювати.

Наприклад, якщо Foo викликає Bar, і Foo потрібно припинити обробку, незалежно від типу винятку, викинутого Bar, чи є якась перевага в тому, щоб конкретно визначити тип виключення, який я ловлю?

Можливо, кращий приклад:

public void Foo()
{
    // Some logic here.
    LogUtility.Log("some message");
}

public static void Log()
{
    try
    {
        // Actual logging here.
    }
    catch (Exception ex)
    {
        // Eat it. Logging failures shouldn't stop us from processing.
    }
}

Якщо ви не ловите загальний виняток тут, вам доведеться спіймати кожен можливий виняток. У Патріка є хороший момент, який OutOfMemoryExceptionне слід вирішувати таким чином. Що робити, якщо я хочу ігнорувати кожен виняток, але OutOfMemoryException?


12
Про що OutOfMemoryException? Той самий код обробки, що і все інше?
Патрік

@Patrick Добре. Я додав новий приклад у своєму питанні, щоб висвітлити ваше запитання.
Боб Хорн

1
Ловлячи загальні винятки, настільки ж погано, як робити загальні твердження про те, що ви всі повинні робити. Зазвичай не існує єдиного підходу до всього.

4
@Patrick, що було б OutOfMemoryError, що саме з цього Exceptionдерева є окремим від спадкового дерева
Darkhogg

Що з винятками з клавіатури, як-от Ctrl-Alt-Del?
Mawg

Відповіді:


33

Ці правила, як правило, є хорошою ідеєю, і тому їх слід дотримуватися.

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

На протилежній стороні аргументу.

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


2
Збіг. По крайней мере , тепер, коли регулярний журнал в автономному режимі офлайн, роблять якесь положення, яке дасть якийсь вихід налагодження для STDERR, якщо нічого іншого.
Шадур

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

1
Цікаво, що log4net явно не хоче зупиняти додаток лише через те, що журнал не вдається. І я думаю, це залежить від того, що ти ведеш реєстрацію; перевірки безпеки, звичайно, вбивають процес. Але налагодження журналів? Не так важливо.
Енді

1
@Andy: Так, все залежить від того, що ти робиш.
Мартін Йорк

2
Чому ти їх не розумієш? І чи не слід ви цього прагнути?
Mawg

31

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

Є кілька типів винятків, з якими ви можете впоратися:

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

О, і як загальне правило: якщо ви не знаєте, що робити з винятком, якщо ви його спіймали, краще просто швидко вийти з ладу (передайте виняток абоненту і дайте йому впоратися)


1
Як щодо конкретного випадку у питанні? Якщо в коді реєстрації є помилка, ймовірно, добре ігнорувати виняток, тому що код, який насправді має значення, не повинен впливати на це.
svick

4
Чому б не виправити помилку в коді реєстрації?
Віктор Хурдугачі

3
Вікторе, це, мабуть, не помилка. Якщо на диску, в якому живе журнал, не вистачає місця, це помилка в коді реєстрації?
Carson63000

4
@ Carson63000 - ну так. Журнали не записуються, отже: помилка. Реалізуйте обертові колоди фіксованого розміру.
detly

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

15

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

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


1
Я погоджуюся з цим теоретично. Але як щодо мого прикладу ведення журналу вище? Що робити, якщо ви просто хочете ігнорувати винятки з реєстрації та продовжувати? Чи потрібно вам ловити кожен виняток, який міг би бути кинутим і обробляти кожен із них, дозволяючи при цьому більш серйозний виняток міхура?
Боб Горн

6
@BobHorn: Два способи дивитися на це. Так, ви можете сказати, що реєстрація не особливо важлива, і якщо вона не працює, вона не повинна зупиняти вашу програму. І це досить справедливо. Але що робити, якщо ваша програма не записується протягом п'яти місяців, і ви не мали уявлення? Я бачив, як це відбувається. І тоді нам потрібні були колоди. Катастрофа. Я б запропонував робити все, що ви можете придумати, щоб зупинити невдачу журналу, краще, ніж ігнорувати це, коли це відбувається. (Але ви не збираєтеся отримати багато консенсусу з цього приводу.)
пдр

@pdr У своєму прикладі реєстрації я зазвичай надсилаю сповіщення електронною поштою. Або у нас є системи для моніторингу журналів, і якщо журнал не буде активно заповнений, наша служба підтримки отримає попередження. Таким чином, ми дізналися про проблему ведення журналу іншими способами.
Боб Горн

@BobHorn ваш приклад ведення журналу вельми придуманий - більшість фреймворків, про які я знаю, мають велику довжину, щоб не викидати жодних винятків, і не викликали б так (як це зробить будь-який "запис журналу, що трапився у рядку X у файлі Y" марним )

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

9

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


5

Дві можливості не є взаємовиключними.

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

try
{
    this.Foo();
}
catch (BarException ex)
{
    Console.WriteLine("Foo has barred!");
}
catch (BazException ex)
{
    Console.WriteLine("Foo has bazzed!");
}
catch (Exception ex)
{
    Console.WriteLine("Unknown exception on Foo!");
    throw;
}

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

Редагувати: З причин, викладених у коментарях, до останнього улову додано погіршення.


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

1
@pdr Напевно ти розумієш, що це надуманий приклад. Сенс полягав у тому, щоб демонструвати ловлю як конкретних, так і загальних винятків, а не як поводитися з ними.
Ротем

@Rotem Я думаю, що у вас тут хороша відповідь. Але у pdr є пункт. Код, таким чином, робить його схожим на ловлю та продовження. Деякі новачки, бачачи цей приклад, можуть подумати, що це хороший підхід.
Боб Хорн

Спростована чи ні, це робить вашу відповідь неправильною. Як тільки ви починаєте ловити винятки, такі як Out Of Memory і Disk Full, і просто проковтуючи їх, ви доводите, чому ловити загальні винятки насправді погано.
пдр

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

5

Я думав про те саме недавно, і мій попередній висновок полягає в тому, що просте питання виникає через те, що ієрархія .NET Exception сильно заплутана.

Візьмемо, наприклад, низький показник, ArgumentNullExceptionякий може бути розумним кандидатом на виняток, якого ви не хочете зловити, оскільки він, як правило, вказує на помилку в коді, а не на помилкову помилку виконання. Ага так. Так само NullReferenceException, за винятком випадків, що NullReferenceExceptionвипливають SystemExceptionбезпосередньо, тому немає жодного відра, в який можна помістити всі "логічні помилки", щоб ловити (або не ловити).

Тоді є головний підхід IMNSHO, який має SEHExceptionотримати (через ExternalException), SystemExceptionі, таким чином, зробити це "нормальним"SystemException Коли ви отримаєтеSEHException, ви хочете написати дамп і закінчити як можна швидше - і, починаючи з .NET 4 хоча б деякі Винятки SEH вважаються винятками з корумпованими державами, які не будуть спіймані. Хороша річ і факт, що робить цеCA1031правило ще більш марним, адже тепер ваш ледачийcatch (Exception)ніяк не зловить ці гірші типи.

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

Там цей твір г Ліпперта з C#слави, звані прикрих Exception , де він виймає корисні категоризації винятків: Ви можете сказати , ви тільки хочете , щоб зловити «екзогенні» з них, за винятком ... C#мови та дизайну .NET винятки з рамки унеможливлюють "спіймання лише екзогенних" будь-яким стислим способом. (І, наприклад, OutOfMemoryExceptionможе дуже добре бути абсолютно нормально невиправна помилка для API , який повинен виділяти буфери, які як - то великий)

Підсумок для мене полягає в тому, що спосіб C#роботи блоків лову і те, як розроблена ієрархія винятків Framework, правило CA1031виходить за межі абсолютно марних . Він вдає , щоб допомогти з кореневої проблемою «не проковтнула виключення» , але ковтання виключення має нульове робити з тим, що ви ловите, але з тим, що ви тоді робите:

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


Як бічна примітка, є функція C # 6 під назвою ФільтриCA1031 винятку, яка знов зробить трохи більш чинною, оскільки ви зможете належним чином, належним чином, правильно фільтрувати винятки, які ви хочете зловити, і причин для написання нефільтрованого менше catch (Exception).


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

1
@supercat - досить правдивий, але це в значній мірі вірно для будь-якого іншого загального винятку системи, коли речі йдуть погано в будь-якому остаточному блоці.
Мартін Ба

1
Це правда, але можна (і взагалі слід) захищати finallyблоки від винятків, за які реально можна очікувати, що вони відбудуться таким чином, що і вихідні, і нові винятки будуть записані в журнал. На жаль, для цього часто потрібно виділення пам'яті, що може призвести до власних збоїв.
supercat

4

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

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

Подивіться на цю відповідь ТА для більшого читання.


1
Ця ситуація (коли розробник повинен бути тренером Pokemon) з'являється, коли ви самостійно створюєте ціле програмне забезпечення: ви створили шари, підключення до бази даних та графічний інтерфейс користувача. Ваш додаток має відновитися після таких ситуацій, як втрата підключення, видалена папка користувача, пошкоджені дані тощо. Кінцевим користувачам не подобаються програми, які виходять з ладу та горять. Їм подобаються додатки, які можуть працювати на палаючому вибуховому комп'ютері!
Broken_Window

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

2
@Magus: За допомогою такого способу LoadDocument()неможливо ідентифікувати всі речі, які можуть піти не так, але 99% винятків, які можуть бути викинуті, просто означатимуть "Не вдалося інтерпретувати вміст файлу із заданим іменем як документ; розібратися з ним ". Якщо хтось намагається відкрити щось, що не є дійсним файлом документа, це не повинно зламати програму та знищити будь-які інші відкриті документи. Поводження з помилками Pokemon у таких випадках некрасиво, але я не знаю жодної хорошої альтернативи.
supercat

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

1
@Magus: Він може кидати всілякі винятки - ось у чому проблема. Часто дуже важко передбачити всі види винятків, які можуть бути викинуті внаслідок недійсних даних у файлі. Якщо ви не використовуєте PokeMon поводження, ви ризикуєте, що програма загине, тому що, наприклад, файл, який завантажували, містив винятково довгий рядок цифр у місці, де потрібно 32-бітове ціле число у форматі десяткових знаків, і числове переповнення сталося в логіка розбору
supercat

1

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

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

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


0

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

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

Крім того, якщо я обробляю конкретні винятки, що робити, якщо код, який я викликаю, змінити, щоб у майбутньому викинути новий виняток? Тепер я повинен змінити свій код, щоб обробити цей новий виняток. Якщо, якщо я просто спіймав Виняток, мій код не повинен змінюватися.

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

Наприклад, якщо Foo викликає Bar, і Foo потрібно припинити обробку, незалежно від типу винятку, викинутого Bar, чи є якась перевага в тому, щоб конкретно визначити тип виключення, який я ловлю?

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

Що робити, якщо я хочу ігнорувати кожен виняток, окрім OutOfMemoryException?

ІМХО, це неправильний спосіб думати про обробку виключень. Ви повинні працювати з білими списками (вилучення типів A і B), а не з чорних списків (вибирайте всі винятки крім X).


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

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

0

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

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

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

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

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


0

Я хотів би вирішити це з логічної точки зору, а не з технічної точки зору,

Тепер я повинен змінити свій код, щоб обробити цей новий виняток.

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

Ваше питання в основному "Що робити, якщо мені байдуже, що пішло не так? Чи справді мені доведеться пережити проблеми, щоб дізнатися, що це було?"

Ось частина краси: ні, ви цього не робите.

"Отже, чи можу я просто дивитись в інший бік, і все те, що неприємне, що вискакує, автоматично підмітається під килимом і робити це з ним?"

Ні, це не так працює.

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

"Але ... але ... тоді один із тих інших винятків, про які я не дбаю, може спричинити збій мого завдання!"

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


-3

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

Не слід ловити загальні винятки і намагатися продовжувати виконання.

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