Почему попытка {…} наконец {…} хороша; попробуй {…} поймать {} плохо?


201

Я видел, как люди говорили, что использовать catch без аргументов - плохая форма, особенно если эта catch ничего не делает:

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();
}

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

Иначе, что такого особенного в конце концов?


7
Прежде чем пытаться поймать тигра, с которым вы не можете справиться, вы должны документально подтвердить свои окончательные пожелания.

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

Ответы:


357

Большая разница в том, что try...catchпроглотит исключение, скрывая тот факт, что произошла ошибка. try..finallyзапустит ваш код очистки, а затем исключение будет продолжаться, чтобы обрабатываться чем-то, что знает, что с ним делать.


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

3
В большинстве случаев более очевидно, почему конкретное исключение возникает на уровне приложения (например, в определенном параметре конфигурации), чем на уровне класса librray.
Марк Сидаде

88
Дэвид - я бы предпочел, чтобы программа быстро выходила из строя, поэтому я мог быть осведомлен о проблеме, а не оставлять программу работающей в неизвестном состоянии.
Эрик Форбс

6
Если ваша программа находится в неизвестном состоянии после исключения, значит, вы делаете код неправильно.
Zan Lynx

41
@DavidArno, любой код, написанный с учетом инкапсуляции, должен обрабатывать только исключения в своей области видимости. Все остальное нужно передать кому-то другому. Если у меня есть приложение, которое получает имя файла от пользователей, затем читает файл, и моя программа чтения файлов получает исключение, открывающее файл, оно должно передать их (или использовать исключение и выдать новое), чтобы приложение могло сказать Эй, файл не открылся, давайте предложим пользователю другой файл. Считыватель файлов не должен иметь возможности запрашивать пользователей или предпринимать какие-либо другие действия в ответ. Его единственная цель - читать файлы.
iheanyi

62

«Наконец» - это утверждение «Что-то, что вы всегда должны делать, чтобы убедиться, что состояние программы нормальное». Поэтому всегда полезно иметь его, если есть вероятность, что исключения могут отбросить состояние программы. Компилятор также делает все возможное, чтобы обеспечить выполнение вашего кода finally.

«Поймать» - это утверждение «Я могу оправиться от этого исключения». Вы должны восстанавливаться только после исключений, которые вы действительно можете исправить - catch без аргументов говорит: «Эй, я могу восстановиться после чего угодно!», Что почти всегда неверно.

Если бы можно было оправиться от каждого исключения, то это было бы действительно семантическим спором о том, что вы заявляете о своем намерении. Однако это не так, и почти наверняка кадры выше ваших будут лучше оснащены для обработки определенных исключений. Поэтому, наконец, используйте, чтобы ваш код очистки запускался бесплатно, но все же пусть более опытные обработчики справятся с этой проблемой.


1
Ваше мнение широко распространено, но, к сожалению, игнорирует еще один важный случай: явно недействительным объект, чьи инварианты могут больше не храниться. Обычным шаблоном является то, что код получает блокировку, вносит некоторые изменения в объект и снимает блокировку. Если исключение возникает после внесения некоторых, но не всех изменений, объект может остаться в недопустимом состоянии. Несмотря на то, что ИМХО должны существовать лучшие альтернативы , я не знаю лучшего способа, чем отловить любое исключение, которое возникает, когда состояние объекта может быть недействительным, явно аннулировать состояние и повторно вызвать.
суперкат

32

Потому что, когда эта единственная строка вызывает исключение, вы об этом не узнаете.

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

Во втором блоке, исключение будет брошено и пузыри вверх , ноreader.Close() по - прежнему гарантированно работать.

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


21

Наконец выполняется ни на что. Таким образом, если ваш блок try был успешным, он выполнится, если ваш блок try завершится неудачей, он затем выполнит блок catch, а затем блок finally.

Кроме того, лучше попробовать использовать следующую конструкцию:

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

Поскольку оператор using автоматически переносится в try / finally, поток автоматически закрывается. (Вам нужно будет поставить try / catch вокруг оператора using, если вы хотите перехватить исключение).


5
Это не правильно. Использование не переносит код с помощью try / catch, оно должно сказать try / finally
pr0nin

8

Хотя следующие 2 кодовых блока эквивалентны, они не равны.

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

try
{
  int i = 1/0;
}
finally
{
  reader.Close();
}
  1. «наконец» - это код, раскрывающий намерения. Вы заявляете компилятору и другим программистам, что этот код должен выполняться независимо от того, что.
  2. если у вас есть несколько блоков catch, и у вас есть код очистки, вам, наконец, нужно. Без, наконец, вы бы дублировали код очистки в каждом блоке catch. (Принцип СУХОЙ)

наконец, блоки особенные. CLR распознает и обрабатывает код внутри блока finally отдельно от блоков catch, а CLR делает все возможное, чтобы гарантировать, что блок finally всегда будет выполняться. Это не просто синтаксический сахар от компилятора.


5

Я согласен с тем, что кажется консенсусом - пустой «улов» плох, потому что он маскирует любое исключение, которое могло произойти в блоке try.

Кроме того, с точки зрения читабельности, когда я вижу блок try, я предполагаю, что будет соответствующий оператор catch. Если вы используете «try» только для того, чтобы убедиться, что ресурсы выделены в блоке «finally», вместо этого вы можете рассмотреть выражение «using» :

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 и после этого выполняется очистка в Окончании.

В случае, если ваш метод не знает, как обрабатывать исключение, но нуждается в очистке, как только это произошло, используйте Try..Finally

Таким образом, исключение распространяется на вызывающие методы и обрабатывается, если в вызывающих методах есть какие-либо подходящие операторы Catch. Если в текущем методе или каком-либо из вызывающих методов нет обработчиков исключений, происходит сбой приложения.

Тем Try..Finallyсамым гарантируется, что локальная очистка выполняется перед распространением исключения на вызывающие методы.


1
Как бы ни был прост этот ответ, он абсолютно лучший. Хорошо иметь привычку try / catch / finally, даже если один из двух последних оставлен пустым. Существуют очень редкие обстоятельства, когда блок catch может существовать и быть пустым, но, по крайней мере, если вы всегда напишите try / catch / finally, вы увидите пустой блок при просмотре кода. Наличие пустого блока finally также полезно. Если вам нужна очистка позже или вам нужно отладить состояние во время исключения, это невероятно полезно.
Джесси Уильямс

3

Блок try..finally будет по-прежнему генерировать любые исключительные ситуации. Все, что finallyнужно сделать, это убедиться, что код очистки запускается до того, как сгенерировано исключение.

Try..catch с пустым catch полностью поглотит любое исключение и скроет тот факт, что это произошло. Читатель будет закрыт, но никто не скажет, произошла ли правильная вещь. Что, если вы намеревались записать i в файл? В этом случае вы не попадете в эту часть кода, а myfile.txt будет пустым. Все ли последующие методы обрабатывают это правильно? Когда вы увидите пустой файл, сможете ли вы правильно угадать, что он пустой, потому что было сгенерировано исключение? Лучше бросить исключение и дать понять, что вы делаете что-то не так.

Другая причина заключается в том, что try..catch сделано так, что это совершенно неправильно. Делая это, вы говорите: «Что бы ни случилось, я справлюсь». Как насчет StackOverflowException, вы можете убрать после этого? Как насчет OutOfMemoryException? В общем, вы должны обрабатывать только те исключения, которые ожидаете и знаете, как их обрабатывать.


2

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

У вас все еще должен быть оператор finally в случае возникновения исключения, чтобы вы могли очистить ресурсы до того, как это исключение будет выдано вызывающей стороне.


2

С точки зрения читабельности, это более явно говорит будущим читателям кода: «этот материал здесь важен, его нужно делать независимо от того, что происходит». Это хорошо.

Кроме того, пустые операторы catch имеют тенденцию иметь определенный «запах» для них. Они могут быть признаком того, что разработчики не продумывают различные возможные исключения и способы их обработки.


2

Наконец, необязательно - нет причины иметь блок «Наконец», если нет ресурсов для очистки.


2

Взяты из: здесь

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

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

Решение о том, проверять ли состояние объекта перед вызовом конкретного метода, который может вызвать исключение, зависит от ожидаемого состояния объекта. Если объект FileStream создается с использованием существующего пути к файлу и конструктора, который должен возвращать файл в режиме чтения, проверка свойства CanRead не требуется; невозможность чтения FileStream будет нарушением ожидаемого поведения вызовов методов, и должно возникнуть исключение. Напротив, если метод задокументирован как возвращающий ссылку FileStream, которая может или не может быть читаемой, рекомендуется проверить свойство CanRead перед попыткой чтения данных.

Чтобы проиллюстрировать влияние на производительность, которое может вызвать использование метода кодирования «выполнение до исключения», производительность приведения, который выдает InvalidCastException в случае сбоя приведения, сравнивается с оператором C # as, который возвращает нули в случае сбоя приведения. Выполнение этих двух приемов одинаково для случая, когда приведение является действительным (см. Тест 8.05), но для случая, когда приведение является недействительным, и использование преобразования приводит к исключению, использование преобразования в 600 раз медленнее, чем использование как оператор (см. тест 8.06). Высокопроизводительное влияние метода выброса исключений включает в себя стоимость выделения, выброса и перехвата исключения, а также стоимость последующей сборки мусора объекта исключения, что означает, что мгновенное воздействие выброса исключения не столь велико. Чем больше исключений,


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


2

Если вы прочитаете C # для программистов, то поймете, что блок finally был разработан для оптимизации приложения и предотвращения утечки памяти.

CLR не полностью устраняет утечки ... утечки памяти могут произойти, если программа непреднамеренно сохранит ссылки на нежелательные объекты

Например, когда вы открываете соединение с файлом или базой данных, ваш компьютер будет выделять память для обслуживания этой транзакции, и эта память будет сохраняться только после выполнения команды disposed или close. но если во время транзакции произошла ошибка, исходящая команда будет прервана, если только она не была внутри try.. finally..блока.

catchотличался от finallyтого, что catch был спроектирован, чтобы дать вам возможность обрабатывать / обрабатывать или интерпретировать ошибку самостоятельно. Думайте об этом как о человеке, который говорит вам: "Эй, я поймал некоторых плохих парней, что ты хочешь, чтобы я сделал с ними?" В то время как 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

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


1

Ну, во-первых, плохая практика - ловить исключения, с которыми вы не справляетесь. Прочтите главу 5 о производительности .Net с точки зрения повышения производительности и масштабируемости приложений .NET . Заметьте, что вы, вероятно, должны загружать поток внутри блока try, таким образом, вы можете перехватить соответствующее исключение в случае сбоя. Создание потока вне блока try отменяет его назначение.


0

Среди многих причин, исключения очень медленно выполняются. Вы можете легко нанести вред вашему времени выполнения, если это часто случается.


0

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

Блоки try / finally позволяют выполнить очистку кода, но при этом быстро завершаются неудачно. В большинстве случаев вам нужно только перехватить все исключения на глобальном уровне, чтобы их можно было зарегистрировать, а затем выйти.


0

Эффективная разница между вашими примерами незначительна, если не выдается никаких исключений.

Однако, если исключение выдается в предложении try, первый пример полностью его проглотит. Второй пример поднимет исключение до следующего шага вверх по стеку вызовов, поэтому разница в указанных примерах заключается в том, что один полностью скрывает любые исключения (первый пример), а другой (второй пример) сохраняет информацию об исключениях для возможной последующей обработки, в то время как по-прежнему выполняем контент в предложении 'finally'.

Например, если вы поместите код в предложение 'catch' в первом примере, который вызвал исключение (либо то, которое было инициировано, либо новое), код очистки считывателя никогда не будет выполнен. Наконец, выполняется независимо от того, что происходит в предложении catch.

Таким образом, основное различие между «catch» и «finally» заключается в том, что можно рассмотреть содержимое блока «finally» (за редкими исключениями). гарантированно выполненным даже в случае непредвиденного исключения, тогда как любой код, следующий за Предложение «catch» (но за исключением предложения «finally») не несет такой гарантии.

Кстати, Stream и StreamReader реализуют IDisposable и могут быть заключены в блок «using». Блоки 'Using' являются семантическим эквивалентом try / finally (без 'catch'), поэтому ваш пример можно выразить более кратко в виде:

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

... который закроет и удалит экземпляр StreamReader, когда он выйдет из области видимости. Надеюсь это поможет.


0

try {…} catch {} не всегда плохо. Это не обычный шаблон, но я склонен использовать его, когда мне нужно отключить ресурсы, несмотря ни на что, например, закрыть (возможно) открытые сокеты в конце потока.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.