Поширення винятків: Коли я можу ловити винятки?


44

MethodA викликає MethodB, який, в свою чергу, викликає MethodC.

У методіB або MethodC немає обробляння винятків. Але в MethodA є обробка винятків.

У MethodC відбувається виняток.

Тепер цей виняток перекидається до MethodA, який обробляє його належним чином.

Що з цим не так?

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

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

Я розумію це, коли потрібно звільнити ресурси. Це зовсім інша справа.


46
Чому, на вашу думку, консенсус полягає в тому, щоб пройти ланцюжок через улов?
Калет

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

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

7
"Що з цим не так?" : нічого
Еван

5
Уловлювальний пристрій (який не містить винятків у різних типах чи щось подібне) перемагає цілі винятку. Кидання винятків - це складний механізм, і він був побудований навмисно. Якщо прохідний улов був випадком призначеного використання, то все, що вам знадобиться, - це реалізувати Result<T>тип (тип, який або зберігає результат обчислення, або помилку), і повернути його зі своїх функцій, що кидаються в іншому випадку. Поширення помилки в стеку спричинить за собою зчитування кожного поверненого значення, перевірку його помилки та повернення помилки, якщо так.
Олександр

Відповіді:


139

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

Єдині причини, чому метод повинен мати механізм уловлювання та повторного відкидання:

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

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


28
@GregBurghardt, якщо у вашій мові є щось подібне try ... finally ..., тоді використовуйте це, а не ловіть і повторюйте
Caleth

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

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

11
@Voo У вашому прикладі ви дійсно знаєте , що робити з ним. Згорніть його в документально винятковому винятку, характерному для вашого коду, наприклад, LoadDataExceptionі включіть оригінальні дані про винятки відповідно до ваших мовних особливостей, щоб майбутні технічні працівники могли бачити першопричину без необхідності приєднувати налагоджувач і з'ясувати, як відтворити проблему.
Колін Янг

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

21

Що з цим не так?

Абсолютно нічого.

Тепер цей виняток перекидається до MethodA, який обробляє його належним чином.

"належним чином обробляє це" є важливою частиною. У цьому суть структурованої обробки виключень.

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

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

Саме це і слід робити. Якщо ви читаєте код, який має обробника / повторного викупування "до кінця вниз", ви [ймовірно] читаєте досить поганий код.

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

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


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

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

@Kevin У цьому випадку повинен бути один catchна найвищому рівні, який записує помилку і повертає відповідь на помилку. Не catchблоки посипані скрізь. Якщо вам не здається перераховувати всі можливі перевірені винятки (на таких мовах, як Java), просто загорніть його RuntimeExceptionзамість того, щоб вносити його туди, намагаючись продовжувати, і наштовхуєтесь на більше помилок або навіть уразливості.
Соломон Учко

8

Ви повинні змінити значення "Бібліотеки" та "Програми".

Бібліотеки можуть вільно кидати неприховані винятки

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

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

Програми завжди повинні бути винятками

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

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

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


Дійсно, якщо у вас є бібліотека для чогось типу операцій з запису на диск або, скажімо, для інших апаратних маніпуляцій, ви можете опинитися у всіляких несподіваних подіях. Що робити, якщо під час запису виймається жорсткий диск? Що за короткий час утримується CD-диск під час читання? Це поза вашим контролем, і хоча ви можете щось зробити (наприклад, зробити вигляд, що це було успішно), часто є хорошою практикою кинути виняток користувачу бібліотеки і дати їм рішення. Можливо, це HDDPluggedOutDuringWritingExceptionможе бути вирішено і не є смертельним для програми. Програма може вирішити, що з цим робити.
ВЛАЗ

1
@VLAZ Що таке фатальне проти нефатальне - це те, що програма повинна вирішити. Бібліотека повинна розповісти, що сталося. Додаток повинен вирішити, як на це реагувати.
MechMK1

0

Що не так з візерунком, який ви описуєте, це те, що метод A не зможе розрізнити три сценарії:

  1. Спосіб B не вдався очікуваним чином.

  2. Метод C не вдався таким чином, як не передбачалося методом B, але поки метод B виконував операцію, від якої можна було б безпечно відмовитися.

  3. Метод C не вдався таким чином, як це не передбачалося методом B, але поки метод B виконував операцію, яка розміщувала речі у передбачуваному тимчасовому непорядному стані, який B не зміг очистити через збій C.

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


2
Сценарії 2 та 3 є помилками у методі B. Метод A не повинен намагатися їх виправити.
Sjoerd

@Sjoerd: Як метод B повинен передбачити всі способи, через які метод C може виявитися невдалим?
supercat

За добре відомими шаблонами, такими як виконання всіх операцій, які можуть кинути в тимчасові змінні, потім з операціями, які не можуть кинути - наприклад, swap - поміняти старий на новий стан. Інша закономірність - це визначення операцій, які можна повторити безпечно, тому ви можете повторити операцію, не боячись зіпсувати. Є повні книги про написання "безпечного коду виключення", тому я не можу вам тут все розповісти.
Sjoerd

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

@Sjoerd Метод B стає набагато простіше міркувати, якщо винятки заборонені мовою. Тому що в цьому випадку ви насправді бачите всі шляхи управління потоком через B, і вам не доведеться вдруге здогадуватися, які оператори можуть бути перевантажені кидком (C ++), щоб уникнути сценарію 3. Ми платимо багато за кодом ясність та безпеку, щоб лінуватися повертати помилки шляхом "лише" кидання винятків. Тому що, врешті-решт, поводження з помилками є важливою частиною коду.
cmaster
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.