Чому спробувати {…} нарешті {...} добре; спробувати {…} зловити {} погано?


201

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

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
catch   // No args, so it will catch any exception
{}
reader.Close();

Однак це вважається хорошою формою:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
finally   // Will execute despite any exception
{
  reader.Close();
}

Наскільки я можу сказати, єдина відмінність між тим, як поставити код очищення в остаточний блок, і поставити код очищення після try..catch блоків, якщо у вашому блоці спробу є повернення заяви (у такому випадку, код очищення, нарешті, буде запустити, але код після спробу..catch не буде).

Інакше, у чому нарешті таке особливе?


7
Перш ніж спробувати зловити тигра, з яким ви не можете впоратися, слід задокументувати свої Останні бажання.

Тема винятків у документації може дати добру інформацію. Погляньте також на приклад " Остаточний блок" .
Афафуд

Відповіді:


357

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


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

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

88
Девід - Я вважаю за краще програму швидко виходити з ладу, щоб я міг усвідомлювати проблему, а не залишати програму у невідомому стані.
Ерік Форбс

6
Якщо ваша програма знаходиться в невідомому стані після винятку, ви робите код неправильно.
Зан Лінкс,

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

62

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

"Ловити" - це твердження "я можу оговтатися після цього винятку". Вам слід оговтатися лише після винятків, які ви справді можете виправити - ловіть без аргументів сказано "Ей, я можу відновитись від усього!", Що майже завжди не відповідає дійсності.

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


1
Ваші настрої широко розповсюджені, але, на жаль, ігнорують ще один важливий випадок: явне визнання недійсним об'єкта, інваріанти якого можуть більше не мати. Поширений зразок полягає в тому, щоб код придбав замок, вніс деякі зміни в об'єкт і звільнив замок. Якщо після внесення деяких, але не всіх змін, виняток виникає, об'єкт може бути залишений у недійсному стані. Незважаючи на те, що в ІМХО повинні існувати кращі альтернативи , я не знаю кращого підходу, ніж ловити будь-який виняток, який має місце, коли стан об'єкта може бути недійсним, явно визнати недійсним стан та повторно скинути.
supercat

32

Тому що, коли цей один рядок кидає виняток, ви цього не знали.

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

У другому блоці, виняток буде кинуто і бульбашки вгору , алеreader.Close() по - , як і раніше гарантовано працювати.

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


21

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

Також краще спробувати використати таку конструкцію:

using (StreamReader reader=new  StreamReader("myfile.txt"))
{
}

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


5
Це неправильно. Використання не
перетворює

8

Хоча наступні 2 кодові блоки еквівалентні, вони не є рівними.

try
{
  int i = 1/0; 
}
catch
{
  reader.Close();
  throw;
}

try
{
  int i = 1/0;
}
finally
{
  reader.Close();
}
  1. "нарешті" - це код, що розкриває намір. Ви заявляєте компілятору та іншим програмістам, що цей код потрібно запускати, незважаючи ні на що.
  2. якщо у вас є кілька блоків вилову, і у вас є код очищення, вам потрібно, нарешті. Зрештою, ви б дублювали код очищення у кожному блоці вилову. (Принцип DRY)

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


5

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

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

using (StreamReader reader = new StreamReader('myfile.txt'))
{
    // do stuff here
} // reader.dispose() is called automatically

Ви можете використовувати оператор 'using' з будь-яким об'єктом, який реалізує IDisposable. Метод dispose () об'єкта викликається автоматично в кінці блоку.


4

Використовуйте Try..Catch..Finally, якщо ваш метод знає, як локально обробляти виняток. Виняток трапляється в Try, Handled in Catch, після чого очищення виконується в Final.

У тому випадку, якщо ваш метод не знає, як обробити виняток, але потребує очищення, як тільки він відбувся Try..Finally

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

Завдяки Try..Finallyцьому забезпечується локальне очищення до розповсюдження винятку з методів виклику.


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

3

Блок спробу .. остаточно все одно викидає будь-які винятки, які виникли. Все finallyце - забезпечити запуск коду очищення перед тим, як викинути виняток.

Спробу..catch з порожнім уловом повністю споживатиме будь-які винятки та приховуватиме те, що це сталося Читач буде закритий, але немає повідомлення про те, чи трапилася правильна річ. Що робити, якщо вашим наміром було написати я у файл? У цьому випадку ви не перейдете до тієї частини коду, і myfile.txt буде порожнім. Чи всі методи нижче за течією справляються з цим правильно? Коли ви побачите порожній файл, чи зможете ви правильно здогадатися, що він порожній, оскільки викинуто виняток? Краще киньте виняток і дайте знати, що ви робите щось не так.

Інша причина - це спроба .. зробити так, як це абсолютно невірно. Що ви говорите, роблячи це: "Незалежно від того, що трапиться, я можу впоратися з цим". А як же StackOverflowException, чи можете ви після цього прибирати? Про що OutOfMemoryException? Взагалі, вам слід поводитися лише з винятками, які ви очікуєте, і знаєте, як впоратися.


2

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

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


2

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

Також порожні заяви про вилов мають, як правило, певний «запах». Це може бути ознакою того, що розробники не замислюються над різними винятками, які можуть виникнути, і як з ними впоратися.



2

Взято з: тут

Збільшення та вилучення винятків не повинно відбуватися як частина успішного виконання методу. Розробляючи бібліотеки класів, клієнтському коду необхідно надати можливість перевірити стан помилки перед тим, як здійснити операцію, що може призвести до виникнення виключення. Наприклад, System.IO.FileStream надає властивість CanRead, яку можна перевірити до виклику методу Read, запобігаючи підвищенню потенційного винятку, як показано у наведеному нижче фрагменті коду:

Dim str As Stream = GetStream () If (str.CanRead) Потім 'код для читання потоку End End

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

Щоб проілюструвати вплив продуктивності, який може спричинити використання методу кодування "запустити до винятку", продуктивність передачі, яка видає InvalidCastException, якщо виникла невдача, порівнюється з оператором C # як оператор, який повертає нулі, якщо випуск не вдається. Виконання двох методів ідентично для випадку, коли амплуа є дійсним (див. Тест 8.05), але для випадку, коли амплуа недійсний, а використання анотації спричиняє виняток, використання кастингу в 600 разів повільніше, ніж використання як оператор (див. Тест 8.06). Високопродуктивний вплив техніки викидання винятків включає витрати на розподіл, викидання та вилов винятку та витрати на подальше вивезення сміття об’єкта виключення, що означає, що миттєвий вплив викидання виключення не такий високий. Оскільки викидів більше,


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


2

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

CLR не повністю усуває витоки ... Витік пам'яті може статися, якщо програма ненавмисно зберігає посилання на небажані об'єкти

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

catchвідрізнявся від того, finallyв тому сенсі, що ловка - це дизайн, який дозволить вам впоратися / керувати чи інтерпретувати помилку, яку я власноруч. Подумайте про це як про людину, яка каже вам: «Ей, я зловив поганих хлопців, що ти хочеш, щоб я їм зробив»? при цьому він finallyбув розроблений так, щоб переконатися, що ваші ресурси розміщені належним чином. Подумайте про когось, чи є чи ні погані хлопці, він переконається, що ваше майно все ще було в безпеці.

І ви повинні дозволити цим двом працювати назавжди.

наприклад:

try
{
  StreamReader reader=new  StreamReader("myfile.txt");
  //do other stuff
}
catch(Exception ex){
 // Create log, or show notification
 generic.Createlog("Error", ex.message);
}
finally   // Will execute despite any exception
{
  reader.Close();
}

1

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


1

Ну для одного - погана практика виловлювати винятки, з якими ви не намагаєтеся впоратися. Ознайомтеся з главою 5 про .Net Performance від покращення продуктивності та масштабування .NET додатків . Бічна примітка, ви, ймовірно, повинні завантажувати потік всередині блоку спробу, таким чином, ви можете зловити відповідний виняток, якщо він не вдасться. Створення потоку поза блоком спробу перемагає його призначення.


0

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


0

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

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


0

Ефективна різниця між вашими прикладами незначна до тих пір, поки не будуть викладені винятки.

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

Якщо, наприклад, ви повинні було ввести код у пункті "catch" першого прикладу, який викинув виняток (або той, який був спочатку піднятий, або новий), код очищення читача ніколи не виконується. Нарешті виконується незалежно від того, що відбувається в пункті "ловлі".

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

Між іншим, Stream і StreamReader реалізують IDisposable і можуть бути загорнуті в блок "використання". Блоки "Використання" - це смисловий еквівалент спроби / нарешті (без "улову"), тому ваш приклад може бути більш виразно виражений як:

using (StreamReader reader = new  StreamReader("myfile.txt"))
{
  int i = 5 / 0;
}

... який закриє і видалить екземпляр StreamReader, коли він вийде за межі області. Сподіваюсь, це допомагає.


0

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

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