Классический способ программирования с try ... catch
. Когда уместно использовать try
без catch
?
В Python следующее кажется законным и может иметь смысл:
try:
#do work
finally:
#do something unconditional
Тем не менее, код ничего не сделал catch
. Точно так же можно подумать, что в Java это будет выглядеть следующим образом:
try {
//for example try to get a database connection
}
finally {
//closeConnection(connection)
}
Это выглядит хорошо, и вдруг мне не нужно беспокоиться о типах исключений и т. Д. Если это хорошая практика, то когда это хорошая практика? В качестве альтернативы, по каким причинам это не является хорошей практикой или не является законным? (Я не скомпилировал исходный код. Я спрашиваю об этом, поскольку это может быть синтаксической ошибкой для Java. Я проверил, что Python обязательно компилируется.)
С этой проблемой я столкнулся: я продолжаю писать функцию / метод, в конце которой она должна что-то вернуть. Тем не менее, это может быть в месте, которое не должно быть достигнуто и должно быть точкой возврата. Таким образом, даже если я обработаю исключения выше, я все еще возвращаю NULL
или пустую строку в некоторой точке кода, которая не должна быть достигнута, часто в конце метода / функции. Мне всегда удавалось реструктурировать код так, чтобы в этом не было необходимости return NULL
, так как это, похоже, выглядит менее чем хорошей практикой.
источник
try/catch
это не «классический способ программирования». Это классический способ программирования на C ++ , потому что в C ++ отсутствует правильная конструкция try / finally, что означает, что вы должны реализовывать гарантированные обратимые изменения состояния, используя некрасивые хаки с использованием RAII. Но приличные языки OO не имеют этой проблемы, потому что они обеспечивают try / finally. Он используется для совсем другой цели, чем попытка / отлов.Ответы:
Это зависит от того, можете ли вы иметь дело с исключениями, которые могут быть подняты в данный момент, или нет.
Если вы можете обрабатывать исключения локально, вы должны это делать, и лучше обрабатывать ошибку как можно ближе к месту ее возникновения.
Если вы не можете обрабатывать их локально, то
try / finally
вполне разумно просто иметь блок - при условии, что есть какой-то код, который вам нужно выполнить, независимо от того, был ли метод успешным или нет. Например (из комментария Нейла ), открытие потока, а затем передача этого потока во внутренний метод, который нужно загрузить, является отличным примером того, когда вам нужноtry { } finally { }
, используя предложение finally, чтобы гарантировать, что поток закрыт, независимо от успеха или сбой чтения.Тем не менее, вам все равно понадобится обработчик исключений где-то в вашем коде - если, конечно, вы не хотите, чтобы ваше приложение полностью аварийно завершало работу. Это зависит от архитектуры вашего приложения, где именно находится этот обработчик.
источник
try { } finally { }
, воспользоваться предложением finally, чтобы гарантировать, что поток в конечном итоге будет закрыт независимо от успеха / отказ.finally
Блок используется для кода , который всегда должен работать, произошло ли условие ошибки (исключение) или нет.Код в
finally
блоке запускается после завершенияtry
блока и, если возникло исключение, после завершения соответствующегоcatch
блока. Он всегда запускается, даже если в блокеtry
or произошло необработанное исключениеcatch
.finally
Блок , как правило , используются для закрытия файлов, сетевых соединений и т.д. , которые были открыты вtry
блоке. Причина в том, что файловое или сетевое соединение должно быть закрыто, независимо от того, была ли успешной операция, использующая этот файл или сетевое соединение, или произошла ошибка.В
finally
блоке следует позаботиться о том, чтобы он сам не выдал исключение. Например, не забудьте проверить все переменныеnull
и т. Д.источник
try-finally
может быть замененоwith
утверждением.try/finally
конструкции.using
with
using
иtry-finally
, так какDispose
метод не будет вызыватьсяusing
блоком, еслиIDisposable
в конструкторе объекта происходит исключение .try-finally
позволяет выполнять код, даже если конструктор объекта выдает исключение.Примером использования try ... finally без оператора catch (и более того, идиоматического ) в Java является использование Lock в пакете одновременных утилит блокировки.
источник
l.lock()
внутрь попробовать?try{ l.lock(); }finally{l.unlock();}
try
в этом фрагменте кода предполагается обернуть доступ к ресурсам, зачем загрязнять его чем-то, не связанным с этимl.lock()
неfinally
получится, блок все равно будет работать, еслиl.lock()
находится внутриtry
блока. Если вы сделаете это так, как предлагает gnat,finally
блок будет работать только тогда, когда мы узнаем, что блокировка была получена.На базовом уровне
catch
иfinally
решить две связанные, но разные проблемы:catch
используется для решения проблемы, о которой сообщил код, который вы назвалиfinally
используется для очистки данных / ресурсов, создаваемых / изменяемых текущим кодом, независимо от того, возникла проблема или нетТак что оба связаны с проблемами (исключениями), но это почти все, что у них общего.
Важным отличием является то, что
finally
блок должен быть в том же методе, где были созданы ресурсы (чтобы избежать утечек ресурсов), и не может быть помещен на другой уровень в стеке вызовов.catch
Однако это совсем другое дело: правильное место для него зависит от того, где вы можете обрабатывать исключение. Нет смысла отлавливать исключение в месте, где вы ничего не можете с этим поделать, поэтому иногда лучше просто позволить ему провалиться.источник
finally
(статически или динамически) вмещающем операторе try ... и при этом быть на 100% герметичным.У @yfeldblum правильный ответ: try-finally без оператора catch обычно следует заменить соответствующей языковой конструкцией.
В C ++ используются RAII и конструкторы / деструкторы; в Python это
with
утверждение; и в C # этоusing
утверждение.Они почти всегда более элегантны, потому что код инициализации и финализации находится в одном месте (абстрагированный объект), а не в двух местах.
источник
На многих языках
finally
оператор также запускается после оператора return. Это означает, что вы можете сделать что-то вроде:Который освобождает ресурсы независимо от того, как метод был завершен с исключением или регулярным оператором возврата.
Хорошо это или плохо - обсуждается, но
try {} finally {}
не всегда ограничивается обработкой исключений.источник
Я мог бы вызвать гнев Pythonistas (не знаю, так как я не часто использую Python) или программистов из других языков с этим ответом, но, на мой взгляд, большинство функций не должно иметь
catch
блока, в идеале. Чтобы показать почему, позвольте мне сопоставить это с ручным распространением кода ошибки, которое мне приходилось делать при работе с Turbo C в конце 80-х и начале 90-х годов.Допустим, у нас есть функция для загрузки изображения или чего-то подобного в ответ на выбор пользователем файла изображения для загрузки, и это написано на C и сборке:
Я опустил некоторые низкоуровневые функции, но мы можем видеть, что я определил различные категории функций с цветовой кодировкой, основываясь на том, какие обязанности они имеют в отношении обработки ошибок.
Точка отказа и восстановления
Теперь никогда не было сложно написать категории функций, которые я называю «возможные точки сбоев» (те, которые
throw
, т. Е.) И функции «исправление ошибок и отчет» (те, которыеcatch
, т. Е.).Эти функции всегда были тривиальны для правильной записи до того, как стала доступна обработка исключений, так как функция, которая может столкнуться с внешним сбоем, например, невозможностью выделить память, может просто вернуть
NULL
или0
или-1
или установить глобальный код ошибки или что-то в этом роде. И восстановление ошибок / создание отчетов всегда было легким, поскольку после того, как вы прошли путь к стеку вызовов до точки, где имеет смысл восстанавливать и сообщать о сбоях, вы просто берете код ошибки и / или сообщение и сообщаете об этом пользователю. И, естественно, функция на листе этой иерархии, которая никогда не сможет работать, независимо от того, как она будет изменена в будущем (Convert Pixel
), очень просто написать правильно (по крайней мере, в отношении обработки ошибок).Распространение ошибок
Однако утомительными функциями, склонными к человеческим ошибкам, были распространители ошибок , те, которые непосредственно не сталкивались с ошибками , но вызывали функции, которые могут давать сбой где-то в глубине иерархии. В этот момент,
Allocate Scanline
возможно, придется обработать ошибку с,malloc
а затем вернуть ошибку доConvert Scanlines
, затемConvert Scanlines
придется проверить эту ошибку и передать ееDecompress Image
, затемDecompress Image->Parse Image
, иParse Image->Load Image
, иLoad Image
команде пользователя, где наконец сообщается об ошибке ,Именно здесь многие люди совершают ошибки, так как для того, чтобы не проверить и не передать ошибку, требуется всего один пропагандист ошибок, когда происходит сбой всей иерархии функций, когда дело доходит до правильной обработки ошибки.
Кроме того, если функции возвращают коды ошибок, мы почти теряем способность, скажем, в 90% нашей кодовой базы возвращать значения, представляющие интерес в случае успеха, так как очень многие функции должны зарезервировать свое возвращаемое значение для возврата кода ошибки в отказ .
Сокращение человеческих ошибок: глобальные коды ошибок
Итак, как мы можем уменьшить вероятность человеческой ошибки? Здесь я мог бы даже вызвать гнев некоторых программистов на C, но, по моему мнению, немедленное улучшение заключается в использовании глобальных кодов ошибок, таких как OpenGL
glGetError
. Это по крайней мере освобождает функции для возврата значимых значений, представляющих интерес в случае успеха. Существуют способы сделать этот потокобезопасным и эффективным, если код ошибки локализован в потоке.Есть также некоторые случаи, когда функция может столкнуться с ошибкой, но для нее относительно безопасно продолжать работать немного дольше, прежде чем она преждевременно вернется в результате обнаружения предыдущей ошибки. Это позволяет этому происходить без необходимости проверять ошибки на 90% вызовов функций, выполняемых в каждой отдельной функции, поэтому она все еще может позволить надлежащую обработку ошибок, не будучи таким тщательным.
Сокращение человеческих ошибок: обработка исключений
Тем не менее, вышеупомянутое решение все еще требует так много функций, чтобы иметь дело с аспектом потока управления распространения ошибок вручную, даже если это могло бы уменьшить количество строк руководства
if error happened, return error
кода типа. Это не устранит ее полностью, поскольку все равно часто требуется хотя бы одно место, проверяющее на наличие ошибок и возвращающее почти все функции распространения ошибок. Так что это когда обработка исключений входит в картину, чтобы спасти день (Сорта).Но ценность обработки исключений здесь состоит в том, чтобы избавить от необходимости иметь дело с аспектом потока управления при ручном распространении ошибок. Это означает, что его значение связано с возможностью избежать необходимости загружать множество
catch
блоков по всей вашей кодовой базе. На приведенной выше диаграмме единственное место, которое должно иметьcatch
блок, - это место,Load Image User Command
где сообщается об ошибке. В идеале больше ничего не должно быть,catch
потому что в противном случае это становится таким же утомительным и подверженным ошибкам, как и обработка кода ошибки.Поэтому, если вы спросите меня, если у вас есть кодовая база, которая действительно выигрывает от элегантной обработки исключений, она должна иметь минимальное количество
catch
блоков (по минимуму я не имею в виду ноль, а больше как единицу для каждого уникального высокого операция конечного пользователя, которая может завершиться неудачей, и, возможно, даже меньше, если все операции пользователя высокого уровня будут вызываться через центральную командную систему).Очистка ресурсов
Однако обработка исключений только решает необходимость избегать ручного обращения с аспектами распространения ошибок при распространении ошибок в исключительных путях, отличных от обычных потоков выполнения. Часто функция, которая служит распространителем ошибок, даже если она делает это автоматически теперь с EH, все равно может получить некоторые ресурсы, которые ей необходимо уничтожить. Например, такая функция может открыть временный файл, который необходимо закрыть перед возвратом из функции, несмотря ни на что, или заблокировать мьютекс, который необходимо разблокировать, несмотря ни на что.
Для этого я мог бы вызвать гнев многих программистов из всех языков, но я думаю, что подход C ++ к этому идеален. Язык вводит деструкторы, которые вызываются детерминированным образом, как только объект выходит из области видимости. Из-за этого коду C ++, который, скажем, блокирует мьютекс через объект мьютекса с областью действия с деструктором, не нужно вручную разблокировать его, поскольку он будет автоматически разблокирован, как только объект выйдет из области видимости, независимо от того, что происходит (даже если исключение встречается). Таким образом, действительно нет необходимости в хорошо написанном коде C ++, чтобы когда-либо иметь дело с очисткой локальных ресурсов.
В языках, в которых отсутствуют деструкторы, им может понадобиться использовать
finally
блок для очистки локальных ресурсов вручную. Тем не менее, это все еще лучше, чем засорять ваш код ручным распространением ошибок, при условии, что у вас нетcatch
исключений во всем безумном месте.Сторнирование внешних побочных эффектов
Это наиболее трудно концептуальное проблему решить. Если какая-либо функция, будь то распространитель ошибок или точка сбоя, вызывает внешние побочные эффекты, то она должна откатиться или "отменить" эти побочные эффекты, чтобы вернуть систему обратно в состояние, как будто операция никогда не выполнялась, вместо " недействительное »состояние, в котором операция на полпути была успешной. Я не знаю ни одного языка, облегчающего эту концептуальную проблему, за исключением языков, которые просто уменьшают необходимость в большинстве функций вызывать внешние побочные эффекты, в первую очередь, как функциональные языки, которые вращаются вокруг неизменности и постоянных структур данных.
Вот
finally
, пожалуй, одно из самых элегантных решений проблемы в языках, вращающихся вокруг изменчивости и побочных эффектов, потому что часто этот тип логики очень специфичен для конкретной функции и не очень хорошо соответствует концепции «очистки ресурсов». ». И я рекомендуюfinally
свободно использовать в этих случаях, чтобы убедиться, что ваша функция обращает побочные эффекты на языках, которые ее поддерживают, независимо от того, нужен ли вамcatch
блок (и опять же, если вы спросите меня, хорошо написанный код должен иметь минимальное количествоcatch
блоки, и всеcatch
блоки должны быть в тех местах, где это наиболее целесообразно, как показано на рисунке выше вLoad Image User Command
).Язык снов
Тем не менее, ИМО
finally
близка к идеалу для устранения побочных эффектов, но не совсем. Нам нужно ввести однуboolean
переменную, чтобы эффективно откатить побочные эффекты в случае преждевременного выхода (из брошенного исключения или иным образом), например так:Если бы я мог когда-либо разработать язык, мой способ решения этой проблемы был бы таким: автоматизировать приведенный выше код:
... с деструкторами, чтобы автоматизировать очистку локальных ресурсов, делая это так, как нам нужно
transaction
,rollback
иcatch
(хотя я все еще хотел бы добавитьfinally
, скажем, работу с ресурсами C, которые не очищают себя). Тем не менее,finally
с помощьюboolean
переменной - самое близкое к тому, чтобы сделать это простым, что, как мне показалось, пока что не хватает языка моей мечты. Второе наиболее простое решение, которое я нашел для этого, - это средства защиты видимости в таких языках, как C ++ и D, но я всегда находил средства защиты видимости немного неловкими в концептуальном плане, поскольку они размывают идею «очистки ресурсов» и «обращения побочных эффектов». На мой взгляд, это очень разные идеи, которые нужно решать по-другому.Моя маленькая несбыточная мечта о языке также сильно вращалась бы вокруг неизменности и постоянных структур данных, чтобы было намного проще, хотя и не обязательно, писать эффективные функции, которым не нужно глубоко копировать массивные структуры данных целиком, даже если функция вызывает нет побочных эффектов.
Заключение
Так или иначе, за исключением моих разговоров, я думаю, что ваш
try/finally
код для закрытия сокета в порядке и великолепен, учитывая, что Python не имеет эквивалента деструкторов в C ++, и я лично считаю, что вы должны использовать это свободно для мест, которые должны обратить побочные эффекты и свести к минимуму количество мест, где вам нужно,catch
чтобы места, где это наиболее целесообразно.источник
Настоятельно рекомендуется отлавливать ошибки / исключения и обрабатывать их аккуратно, даже если это не обязательно.
Причина, по которой я это говорю, заключается в том, что я считаю, что каждый разработчик должен знать и учитывать поведение своего приложения, иначе он не выполнил свою работу должным образом. Не существует ситуации, в которой блок try-finally заменяет блок try-catch-finally.
Я приведу простой пример: предположим, что вы написали код для загрузки файлов на сервер без перехвата исключений. Теперь, если по какой-либо причине загрузка не удалась, клиент никогда не узнает, что пошло не так. Но, если вы поймали исключение, вы можете отобразить аккуратное сообщение об ошибке, объясняющее, что пошло не так и как пользователь может исправить это.
Золотое правило: всегда ловите исключение, потому что угадывание требует времени
источник
catch
пункт должен присутствовать всегда, когда естьtry
. Более опытные участники, включая меня, считают, что это плохой совет. Ваши рассуждения ошибочны. Метод, содержащийtry
не единственное возможное место, где может быть поймано исключение. Зачастую проще и лучше разрешать ловить и сообщать об исключениях с самого верхнего уровня, чем дублировать предложения catch во всем коде.