Распространение исключений: когда я должен ловить исключения?


44

MethodA вызывает MethodB, который, в свою очередь, вызывает MethodC.

Нет обработки исключений в MethodB или MethodC. Но в MethodA есть обработка исключений.

В MethodC происходит исключение.

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

Что не так с этим?

На мой взгляд, в какой-то момент вызывающая сторона выполнит MethodB или MethodC, и когда в этих методах произойдут исключения, что будет получено от обработки исключений внутри этих методов, которая по сути является просто блоком try / catch / finally, а не просто они пузырились до звонящего?

Оператор или консенсус в отношении обработки исключений - это выброс, когда выполнение не может быть продолжено только из-за этого - исключение. Я понимаю. Но почему бы не поймать исключение дальше вверх по цепочке, вместо того, чтобы блоки try / catch полностью опустились вниз.

Я понимаю, когда вам нужно освободить ресурсы. Это совсем другое дело.


46
Как вы думаете, почему консенсус заключается в том, чтобы иметь цепочку проходов через уловы?
Caleth

С хорошей IDE и надлежащим стилем кодирования вы можете знать, что при вызове метода может возникнуть какое-то исключение. Обрабатывать его или разрешать его распространение - это решение вызывающего абонента. Я не вижу никаких проблем с этим.
Хиеу Ле

14
Если метод не может обработать исключение и просто перебрасывает его, я бы сказал, что это запах кода. Если метод не может обработать исключение, и ему не нужно ничего делать, когда генерируется исключение, тогда try-catchблок вообще не нужен .
Грег Бургард

7
"Что не так с этим?" : nothing
Эван

5
Сквозная отловка (которая не переносит исключения в разные типы или что-то в этом роде) разрушает всю цель исключений. Исключение - сложный механизм, и он был построен намеренно. Если сквозные уловки были предполагаемым вариантом использования, тогда все, что вам нужно, это реализовать Result<T>тип (тип, который либо хранит результат вычисления, либо ошибку) и возвращать его из ваших иначе бросающих функций. Распространение ошибки вверх по стеку повлечет за собой чтение каждого возвращаемого значения, проверку, является ли оно ошибкой, и возврат ошибки, если это так.
Александр

Ответы:


139

Как правило, не ловите исключения, если вы не знаете, что с ними делать. Если MethodC выдает исключение, но у MethodB нет полезного способа его обработать, то это должно позволить распространению исключения до MethodA.

Единственные причины, по которым метод должен иметь механизм захвата и отбрасывания:

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

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


28
@GregBurghardt, если в твоем языке есть что-то похожее try ... finally ..., то используй это, а не лови и перебрасывай
Калет

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

7
«не ловите исключения, если вы не знаете, что с ними делать». На первый взгляд это звучит разумно, но в дальнейшем вызывает проблемы. То, что вы делаете здесь, это утечка деталей реализации для ваших абонентов. Представьте, что вы используете определенный ORM в своей реализации для загрузки данных. Если вы не уловили специфические исключения ORM, а просто позволили им всплыть, вы не сможете заменить слой данных, не нарушив совместимость с существующими пользователями. Это один из наиболее очевидных случаев, но он может стать довольно коварным и его трудно уловить.
Во

11
@Voo В вашем примере вы действительно знаете , что делать с ним. Оберните его в документированное исключение, характерное для вашего кода, например, LoadDataExceptionи включите исходные подробности об исключении в соответствии с вашими языковыми особенностями, чтобы будущие сопровождающие могли видеть основную причину без необходимости подключать отладчик и выяснять, как воспроизвести проблему.
Колин Янг

14
@Voo Похоже, вы пропустили причину «Вы хотите преобразовать одно исключение в другое, которое более важно для вызывающего абонента», для сценариев «поймать / отбросить».
jpmc26

21

Что не так с этим?

Совершенно ничего.

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

«обрабатывает это соответствующим образом» является важной частью. В этом суть структурированной обработки исключений.

Если ваш код может сделать что-то «полезное» с исключением, сделайте это. Если нет, то оставьте в покое.

, , , почему бы не перехватить исключение дальше вверх по цепочке, вместо того, чтобы блоки try / catch полностью опустились.

Это именно то, что вы должны делать. Если вы читаете код, у которого обработчик / обработчик «полностью выключен», то вы, вероятно, читаете какой-то довольно плохой код.

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

Часть трудностей здесь заключается в том, что в большинстве случаев эту проблему даже не замечают, потому что исключения не генерируются все время, но когда они есть , программа будет тратить очень много времени и Постепенно отрывая стек вызовов, чтобы добраться до места, которое действительно делает что-то полезное с Исключением.


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

1
@SolomonUcko: Ну, это зависит. Если, например, вы пишете простой RPC-сервер, а необработанное исключение пузырится на всем пути вплоть до основного цикла обработки событий, единственно разумным вариантом является его регистрация, отправка ошибки RPC удаленному узлу и возобновление обработки событий. Другой альтернативой является уничтожение всей программы, что сведет вас с ума, когда сервер умрет в работе.
Кевин

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

8

Вы должны сделать разницу между библиотеками и приложениями.

Библиотеки могут свободно генерировать неисследованные исключения

Когда вы проектируете библиотеку, в какой-то момент вы должны подумать о том, что может пойти не так. Параметры могут находиться в неправильном диапазоне или nullвнешние ресурсы могут быть недоступны и т. Д.

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

Приложения должны всегда в какой-то момент ловить исключения

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

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

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


Действительно, если у вас есть библиотека для чего-то вроде операций записи на диск или, скажем, других аппаратных манипуляций, вы можете столкнуться с неожиданными событиями любого рода. Что, если во время записи выдернуть жесткий диск? Какой привод компакт-дисков отключается во время чтения? Это вне вашего контроля, и хотя вы могли бы что-то сделать (например, притвориться, что это было успешно), часто бывает полезно бросить исключение пользователю библиотеки и позволить ему принять решение. Может быть, HDDPluggedOutDuringWritingExceptionможно иметь дело с и не является фатальным для приложения. Программа может решить, что с этим делать.
ВЛАЗ

1
@VLAZ То, что является фатальным по сравнению с нефатальным, - это то, что приложение должно решить. Библиотека должна рассказать, что случилось. Приложение должно решить, как реагировать на это.
МечМК1

0

Что не так с шаблоном, который вы описываете, так это то, что метод А не будет иметь возможности различать три сценария:

  1. Метод Б провалился ожидаемым образом.

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

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

Единственный способ, которым метод А сможет отличить эти сценарии, будет в том случае, если выбрасываемое из B исключение включает в себя информацию, достаточную для этой цели, или если разматывание стека для метода B приводит к тому, что объект остается в явно недействительном состоянии. К сожалению, большинство структур исключений делают оба шаблона неловкими, заставляя программистов принимать «меньшие злые» дизайнерские решения.


2
Сценарии 2 и 3 являются ошибками в методе B. Метод A не должен пытаться их исправлять.
Sjoerd

@Sjoerd: Как метод B должен предвидеть все пути, в которых метод C может потерпеть неудачу?
суперкат

Хорошо известными шаблонами, такими как выполнение всех операций, которые могут генерировать временные переменные, затем операции, которые не могут генерировать - например, swap - заменять старое на новое состояние. Другой шаблон - определение операций, которые можно безопасно повторить, чтобы вы могли повторить операцию, не опасаясь путаницы. Существуют полные книги о написании «кода безопасности исключений», поэтому я не могу рассказать вам все здесь.
Sjoerd

Это хороший момент, чтобы вообще не использовать исключения (что было бы отличным решением imho). Но я полагаю, что он на самом деле не отвечает на этот вопрос, поскольку ФП, похоже, намеревается использовать исключения в первую очередь, а только спрашивает, где должен быть улов.
Мастер

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