Аргументи проти придушення помилок


35

Я знайшов подібний фрагмент коду в одному з наших проектів:

    SomeClass QueryServer(string args)
    {
        try
        {
            return SomeClass.Parse(_server.Query(args));
        }
        catch (Exception)
        {
            return null;
        }
    }

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

Коли доцільно повністю придушити всі подібні помилки?


13
Ерік Ліпперт написав чудову публікацію в блозі під назвою неприємні винятки, де він класифікує винятки на 4 різні категорії та пропонує який правильний підхід для кожної з них. Написано з точки зору C #, але я вважаю, що застосовано для різних мов.
Damien_The_Unbeliever

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


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

Відповіді:


52

Уявіть собі код з тисячами файлів за допомогою безлічі бібліотек. Уявіть, що всі вони закодовані так.

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

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

Навіть без надійної обробки помилок просто робити

throw new IllegalStateException("THIS SHOULD NOT HAPPENING")

або

LOGGER.error("[method name]/[arguments] should not be there")

може зекономити години.

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

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

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


7
+1 для пояснення, чому ви вважаєте, що вам потрібно зробити це в коментарі. Без пояснень, ковтання винятку завжди підніме дзвіночки тривоги в моїй голові.
jpmc26

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

@ jpmc26 Якщо обробляється виняток (наприклад, повертаючи спеціальне значення), це зовсім не є викидом з проковтуванням.
Дедуплікатор

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

@Deduplicator так, але використання лову як складової алгоритму теж не є гарною
умовою

14

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

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

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


2
"використовується тоді, коли генерований виняток ніколи не повинен був бути" такого подібного. ТОЧКА винятку - сигналізувати про те, що щось жахливо пішло не так.
Ейфорія

3
@Euphoric Але іноді автор бібліотеки не знає намірів кінцевого користувача цієї бібліотеки. «Страшенно неправильно» однієї людини може бути чужий стан, який легко піддається лікуванню.
Саймон Б

2
@Euphoric, це залежить від того, що ваша мова вважає "жахливо неправильним": Python люблять винятки для речей, які дуже близькі до контролю потоку в (наприклад,) C ++. Повернення нуля може бути захищеним у деяких ситуаціях - наприклад, читання з кеша, де елементи можуть бути раптово і непередбачувано виселені.
Метт Крауз

10
@Euphoric: "Файл не знайдено - це не нормальний виняток. Спершу слід перевірити, чи існує файл, якщо існує можливість, він може не існувати." Ні! Ні! Ні! Ні! Це класичне неправильне мислення навколо атомних операцій. Файл може бути видалений між вами, перевіряючи, чи існує він, і отримувати доступ до нього. Єдиний правильний спосіб вирішити подібні ситуації - це отримати доступ до нього та обробляти винятки, якщо вони трапляються, наприклад, повернувшись, nullякщо це найкраще, що може обрати ваша обрана мова.
Девід Арно

3
@Euphoric: Отже, написати код для обробки, не маючи доступу до файлу принаймні двічі? Це порушує DRY, а також порушений код порушено.
Дедуплікатор

7

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

   if(QueryServer(args)==null)
      TerminateProgram();

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

  result = QueryServer(args);
  if(result!=null)
      DoSomethingMeaningful(result);
  // else ??? Mask the error and let the user run into trouble later

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


5

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

Дві ситуації такі:

1. Коли виняток не вважався винятковим станом

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

Можуть мати на увазі деякі необов’язкові атрибути класу.

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

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

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

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


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

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


-1

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

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

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

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


2
Це не дає відповіді на питання, оскільки обробка винятку, не знаючи деталей про виняток! = Мовчки споживає (загальний) виняток.
Taemyr

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

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

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

-2

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

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

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


"серйозно інертний" - ти мабуть мав на увазі "серйозно невмілий"?
Езотеричне ім'я екрана

@EsotericScreenName Виберіть один ... ;-)
Роббі Ді

-3

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

  • Реалізуй саме те, що мені потрібно. Може бути складним у встановлений термін.
  • Змініть сторонній код. Але тоді я повинен шукати конфлікти злиття з кожним оновленням.
  • Напишіть метод обгортки, щоб перетворити виняток у звичайний нульовий покажчик, булева помилка чи інше.

Наприклад, бібліотечний метод

public foo findFoo() {...} 

повертає перший foo, але якщо його немає, він викидає виняток. Але мені потрібно метод повернення першого або нуля. Тому я пишу це:

public foo myFindFoo() {
    try {
        return findFoo() 
    } 
    catch (NoFooException ex) {
        return null;
    }
}

Не акуратно. Але іноді прагматичне рішення.


1
Що ви маєте на увазі "кидайте винятки там, де вони повинні працювати на вас"?
corsiKa

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