Мой вопрос в том, что большинство разработчиков предпочитают для обработки ошибок, исключений или кодов возврата ошибок. Укажите конкретный язык (или языковую семью) и укажите, почему вы предпочитаете один из них другому.
Я спрашиваю об этом из любопытства. Лично я предпочитаю коды возврата ошибок, поскольку они менее взрывоопасны и не заставляют пользовательский код платить штраф за исключительную производительность, если они этого не хотят.
обновление: спасибо за все ответы! Я должен сказать, что хотя мне не нравится непредсказуемость потока кода с исключениями. Ответ о коде возврата (и ручках их старшего брата) действительно добавляет к коду много шума.
language-agnostic
exception
Роберт Гулд
источник
источник
Ответы:
Для некоторых языков (например, C ++) утечка ресурсов не должна быть причиной
C ++ основан на RAII.
Если у вас есть код, который может завершиться ошибкой, возвратом или выбросом (то есть наиболее обычным кодом), тогда ваш указатель должен быть заключен в интеллектуальный указатель (при условии, что у вас есть очень веская причина не создавать ваш объект в стеке).
Коды возврата более подробны
Они многословны и имеют тенденцию развиваться во что-то вроде:
В конце концов, ваш код представляет собой набор идентифицированных инструкций (я видел такой код в производственном коде).
Этот код можно было бы перевести на:
Которая четко разделяет код и обработку ошибок, что может быть хорошо .
Коды возврата более хрупкие
Если не какое-то непонятное предупреждение от одного компилятора (см. Комментарий "phjr"), их можно легко проигнорировать.
В приведенных выше примерах предположим, что кто-то забыл обработать свою возможную ошибку (это происходит ...). Ошибка игнорируется при "возврате" и, возможно, позже взорвется (например, указатель NULL). Такой же проблемы за исключением не будет.
Ошибка не будет проигнорирована. Но иногда хочется, чтобы он не взорвался ... Так что выбирать нужно внимательно.
Коды возврата иногда необходимо переводить
Допустим, у нас есть следующие функции:
Каков тип возврата doTryToDoSomethingWithAllThisMess, если одна из его вызываемых функций не работает?
Коды возврата - не универсальное решение
Операторы не могут вернуть код ошибки. Конструкторы C ++ тоже не могут.
Коды возврата означают, что вы не можете связывать выражения
Следствие из вышеизложенного. Что, если я хочу написать:
Я не могу, потому что возвращаемое значение уже используется (а иногда его нельзя изменить). Таким образом, возвращаемое значение становится первым параметром, отправленным как ссылка ... Или нет.
Тип исключения
Вы можете отправлять разные классы для каждого типа исключения. Исключения ресурсов (т.е. нехватка памяти) должны быть легкими, но все остальное может быть настолько тяжелым, насколько необходимо (мне нравится исключение Java, дающее мне весь стек).
Затем каждый улов может быть специализирован.
Никогда не используйте catch (...) без повторного броска
Обычно не стоит скрывать ошибку. Если вы не перебрасываете, по крайней мере, зарегистрируйте ошибку в файле, откройте окно сообщения, что угодно ...
Исключение составляют ... NUKE
Проблема с исключением состоит в том, что их чрезмерное использование приведет к созданию кода, полного попыток / уловок. Но проблема в другом: кто пытается / поймать свой код с помощью контейнера STL? Тем не менее, эти контейнеры могут отправлять исключение.
Конечно, в C ++ никогда не позволяйте исключению выйти из деструктора.
Исключение составляют ... синхронные
Обязательно поймайте их, прежде чем они поставят вашу нить на колени или распространятся внутри вашего цикла сообщений Windows.
Решением может быть их смешивание?
Думаю, выход - бросить, когда чего-то не должно случиться. А когда что-то может случиться, используйте код возврата или параметр, чтобы пользователь мог на это реагировать.
Итак, единственный вопрос - «чего не должно происходить?»
Это зависит от контракта вашей функции. Если функция принимает указатель, но указывает, что указатель должен быть отличным от NULL, тогда нормально генерировать исключение, когда пользователь отправляет NULL указатель (вопрос в том, что в C ++, когда автор функции не использовал ссылки вместо этого указателей, но ...)
Другое решение - показать ошибку
Иногда ваша проблема в том, что вам не нужны ошибки. Использование исключений или кодов возврата ошибок - это круто, но ... Вы хотите знать об этом.
В своей работе мы используем своего рода «Утверждение». В зависимости от значений файла конфигурации независимо от параметров компиляции отладки / выпуска:
И при разработке, и при тестировании это позволяет пользователю точно определить проблему, когда она обнаружена, а не после (когда некоторый код заботится о возвращаемом значении или внутри улова).
Его легко добавить в унаследованный код. Например:
приводит своего рода код, похожий на:
(У меня есть похожие макросы, которые активны только при отладке).
Обратите внимание, что на производстве файл конфигурации не существует, поэтому клиент никогда не видит результат этого макроса ... Но его легко активировать, когда это необходимо.
Вывод
Когда вы кодируете с использованием кодов возврата, вы готовитесь к неудаче и надеетесь, что ваша крепость тестов достаточно безопасна.
Когда вы пишете код с использованием исключения, вы знаете, что ваш код может дать сбой, и обычно ставите контрфайер в выбранную стратегическую позицию в вашем коде. Но обычно ваш код больше о том, «что он должен делать», чем о том, «что, как я боюсь, произойдет».
Но когда вы вообще пишете код, вы должны использовать лучший инструмент, который есть в вашем распоряжении, и иногда это «Никогда не скрывайте ошибку и показывайте ее как можно скорее». Макрос, о котором я говорил выше, следует этой философии.
источник
if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
doSomething(); doSomethingElse(); ...
лучше, потому что если мне нужно добавить if / while / etc. операторы для нормального выполнения, я не хочу, чтобы они смешивались с if / while / etc. операторы, добавленные для исключительных целей ... И поскольку реальное правило использования исключений - это выбросить , а не поймать , операторы try / catch обычно не являются инвазивными.На самом деле я использую оба.
Я использую коды возврата, если это известная возможная ошибка. Если это сценарий, который, как я знаю, может и случится, то есть код, который будет отправлен обратно.
Исключения используются исключительно для вещей, которых я НЕ жду.
источник
Согласно главе 7, озаглавленной «Исключения» в документе Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries , дается множество объяснений того, почему использование исключений вместо возвращаемых значений необходимо для объектно-ориентированных структур, таких как C #.
Возможно, это самая веская причина (стр. 179):
«Исключения хорошо интегрируются с объектно-ориентированными языками. Объектно-ориентированные языки, как правило, накладывают ограничения на сигнатуры членов, которые не налагаются функциями на языках, отличных от объектно-ориентированных. Например, в случае конструкторов, перегрузок операторов и свойств разработчик не имеет выбора в возвращаемом значении. По этой причине невозможно стандартизировать создание отчетов об ошибках на основе возвращаемых значений для объектно-ориентированных сред. Метод сообщения об ошибках, например исключения, который выходит за рамки сигнатуры метода это единственный вариант ".
источник
Я предпочитаю (в C ++ и Python) использовать исключения. Предоставляемые на языке средства делают четко определенным процесс как для вызова, так и для перехвата и (при необходимости) повторного вызова исключений, что упрощает просмотр и использование модели. По идее, он чище, чем коды возврата, поскольку конкретные исключения могут определяться по их именам и иметь дополнительную информацию, сопровождающую их. С кодом возврата вы ограничены только значением ошибки (если вы не хотите определить объект ReturnStatus или что-то еще).
Если код, который вы пишете, не критичен по времени, накладные расходы, связанные с раскруткой стека, недостаточно значительны, чтобы о них беспокоиться.
источник
Исключения следует возвращать только тогда, когда происходит что-то, чего вы не ожидали.
Другой момент исключения, исторически, заключается в том, что коды возврата по своей сути являются проприетарными, иногда 0 может быть возвращен из функции C, чтобы указать на успех, иногда -1, или любой из них в случае неудачи с 1 для успеха. Даже когда они перечисляются, перечисления могут быть неоднозначными.
Исключения также могут предоставить гораздо больше информации и, в частности, хорошо разобрать: «Что-то пошло не так, вот что, трассировка стека и некоторая вспомогательная информация для контекста»
При этом хорошо пронумерованный код возврата может быть полезен для известного набора результатов, простого «здесь n результатов функции, и она просто выполнялась таким образом»
источник
В Java я использую (в следующем порядке):
Разработка по контракту (обеспечение выполнения предварительных условий перед попыткой чего-либо, что может потерпеть неудачу). Это улавливает большинство вещей, и я возвращаю для этого код ошибки.
Возврат кодов ошибок во время обработки работы (и выполнение отката при необходимости).
Исключения, но они используются только для неожиданных вещей.
источник
assert
в том, что он не обнаружит проблем в производственном коде, если вы не запускаете все время с утверждениями. Я склонен рассматривать утверждения как способ отловить проблемы во время разработки, но только для вещей, которые будут утверждать или не утверждать последовательно (например, вещи с константами, а не вещи с переменными или что-то еще, что может измениться во время выполнения).Коды возврата почти каждый раз не проходят тест « Яма успеха ».
Что касается производительности:
источник
Отличный совет, который я получил от программиста-прагматика, был примерно такой: «ваша программа должна иметь возможность выполнять все свои основные функции без использования исключений вообще».
источник
Я написал об этом в блоге некоторое время назад.
Накладные расходы на производительность при генерации исключения не должны влиять на ваше решение. В конце концов, если вы все делаете правильно, исключение будет исключением .
источник
Мне не нравятся коды возврата, потому что они вызывают появление в вашем коде следующего шаблона
CRetType obReturn = CODE_SUCCESS; obReturn = CallMyFunctionWhichReturnsCodes(); if (obReturn == CODE_BLOW_UP) { // bail out goto FunctionExit; }
Вскоре вызов метода, состоящий из 4 вызовов функций, превратится в 12 строк обработки ошибок. Некоторые из них никогда не произойдут. Если и переключать случаи предостаточно.
Исключения будут более чистыми, если вы их правильно используете ... чтобы сигнализировать об исключительных событиях ... после которых путь выполнения не может продолжаться. Они часто более описательны и информативны, чем коды ошибок.
Если у вас есть несколько состояний после вызова метода, которые должны обрабатываться по-другому (и не являются исключительными случаями), используйте коды ошибок или параметры out. Хотя лично я обнаружил, что это бывает редко ..
Я немного искал контраргумент о «потере производительности» ... больше в мире C ++ / COM, но в новых языках, я думаю, разница не так уж велика. В любом случае, когда что-то взрывается, проблемы с производительностью уходят на второй план :)
источник
Для любого достойного компилятора или среды выполнения исключения не влекут за собой значительных штрафов. Это более или менее похоже на оператор GOTO, который переходит к обработчику исключения. Кроме того, наличие исключений, обнаруженных средой выполнения (например, JVM), помогает намного проще изолировать и исправить ошибку. Я приму исключение NullPointerException в Java вместо segfault в C в любой день.
источник
finally
блоками и деструкторами для объектов, выделенных стеком.У меня есть простой набор правил:
1) Используйте коды возврата для вещей, на которые вы ожидаете реакции вашего непосредственного вызывающего абонента.
2) Используйте исключения для ошибок, которые имеют более широкий охват, и можно разумно ожидать, что они будут обрабатываться чем-то на много уровней выше вызывающего, чтобы осознание ошибки не проникало через многие уровни, делая код более сложным.
В Java я когда-либо использовал только непроверенные исключения, проверенные исключения в конечном итоге были просто еще одной формой кода возврата, и, по моему опыту, двойственность того, что могло быть «возвращено» вызовом метода, обычно была скорее препятствием, чем помощью.
источник
Я использую исключения в python как в исключительных, так и в неисключительных обстоятельствах.
Часто приятно иметь возможность использовать Exception, чтобы указать, что «запрос не может быть выполнен», в отличие от возврата значения Error. Это означает, что вы / всегда / знаете, что возвращаемое значение имеет правильный тип, а не произвольно None или NotFoundSingleton или что-то в этом роде. Вот хороший пример того, где я предпочитаю использовать обработчик исключений вместо условия для возвращаемого значения.
Побочный эффект заключается в том, что при запуске datastore.fetch (obj_id) вам никогда не нужно проверять, является ли его возвращаемое значение None, вы получите эту ошибку немедленно бесплатно. Это противоречит аргументу «ваша программа должна иметь возможность выполнять все свои основные функции без использования исключений».
Вот еще один пример того, как исключения являются «исключительно» полезными для написания кода для работы с файловой системой, не подверженной условиям гонки.
Один системный вызов вместо двух, состояния гонки нет. Это плохой пример, потому что, очевидно, это приведет к ошибке OSError в большем количестве обстоятельств, чем каталог не существует, но это «достаточно хорошее» решение для многих строго контролируемых ситуаций.
источник
Я считаю, что коды возврата добавляют к шуму кода. Например, мне всегда не нравился внешний вид кода COM / ATL из-за кодов возврата. Для каждой строки кода должна быть проверка HRESULT. Я считаю, что код возврата ошибки - одно из плохих решений, принятых архитекторами COM. Это затрудняет логическую группировку кода, поэтому становится трудным проверка кода.
Я не уверен в сравнении производительности, когда в каждой строке явно проверяется код возврата.
источник
Я предпочитаю использовать исключения для обработки ошибок и возвращать значения (или параметры) как нормальный результат функции. Это дает простую и последовательную схему обработки ошибок, и, если все сделано правильно, код выглядит намного чище.
источник
Одно из больших различий заключается в том, что исключения заставляют вас обрабатывать ошибку, тогда как коды возврата ошибок могут не проверяться.
Коды возврата ошибок, если они используются интенсивно, также могут вызвать очень уродливый код с большим количеством тестов if, аналогичных этой форме:
Лично я предпочитаю использовать исключения для ошибок, которые ДОЛЖНЫ или ДОЛЖНЫ быть обработаны вызывающим кодом, и использовать коды ошибок только для «ожидаемых сбоев», когда возврат чего-либо действительно действителен и возможен.
источник
Есть много причин предпочесть Исключения коду возврата:
источник
Исключения не для обработки ошибок, ИМО. Исключения только что; исключительные события, которых вы не ожидали. Я говорю: используйте с осторожностью.
Коды ошибок могут быть в порядке, но возвращение 404 или 200 из метода - это плохо, ИМО. Вместо этого используйте перечисления (.Net), что сделает код более читаемым и более простым в использовании для других разработчиков. Также вам не нужно вести таблицу с числами и описаниями.
Также; паттерн «попробуй-поймай-наконец» - это антипаттерн в моей книге. Попробовать наконец-то может быть хорошо, пробовать-поймать тоже можно, но пробовать-наконец-то никогда не бывает. try-finally часто можно заменить оператором "using" (шаблон IDispose), что лучше IMO. И попытаться поймать, где вы действительно поймаете исключение, которое вы можете обработать, хорошо, или если вы сделаете это:
Итак, пока вы позволяете исключению продолжать пузыриться, все в порядке. Другой пример:
Здесь я действительно обрабатываю исключение; то, что я делаю за пределами try-catch, когда метод обновления терпит неудачу, - это другая история, но я думаю, что моя точка зрения была высказана. :)
Почему же тогда попытка-поймать-наконец - это анти-паттерн? Вот почему:
Что произойдет, если объект db уже закрыт? Выдается новое исключение, которое необходимо обработать! Это лучше:
Или, если объект db не реализует IDisposable, сделайте следующее:
В любом случае это мои 2 цента! :)
источник
Я использую только исключения, без кодов возврата. Я говорю о Java здесь.
Общее правило,
doFoo()
которому я следую: если у меня вызывается метод, то из этого следует, что если он не выполняет «foo», как это было, то произошло что-то исключительное и должно быть сгенерировано Exception.источник
Единственное, чего я опасаюсь по поводу исключений, - это то, что выброс исключения испортит поток кода. Например, если вы сделаете
Или, что еще хуже, если бы я удалил то, чего не должен был, но меня бросили, чтобы поймать, прежде чем я выполнил оставшуюся часть очистки. Бросок давил на бедного пользователя ИМХО.
источник
Мое общее правило в аргументе исключения и кода возврата:
источник
Я не считаю, что коды возврата менее уродливы, чем исключения. За исключением, у вас есть
try{} catch() {} finally {}
где, как и с кодами возврата, у вас естьif(){}
. Раньше я боялся исключений по причинам, указанным в посте; вы не знаете, нужно ли очистить указатель, что у вас есть. Но я думаю, что у вас те же проблемы, когда дело касается кодов возврата. Вы не знаете состояние параметров, если не знаете некоторых подробностей о рассматриваемой функции / методе.В любом случае, если возможно, вы должны обработать ошибку. Вы можете так же легко позволить исключению распространиться на верхний уровень, как игнорировать код возврата и позволить программе segfault.
Мне нравится идея возврата значения (перечисления?) Для результатов и исключения в исключительном случае.
источник
Для такого языка, как Java, я бы выбрал Exception, потому что компилятор выдает ошибку времени компиляции, если исключения не обрабатываются. Это заставляет вызывающую функцию обрабатывать / генерировать исключения.
Что касается Python, у меня больше противоречий. Компилятора нет, поэтому возможно, что вызывающая сторона не обрабатывает исключение, созданное функцией, ведущее к исключениям времени выполнения. Если вы используете коды возврата, вы можете иметь неожиданное поведение, если не обрабатываете их должным образом, а если вы используете исключения, вы можете получить исключения во время выполнения.
источник
Я обычно предпочитаю коды возврата, потому что они позволяют вызывающей стороне решить, является ли сбой исключительным .
Такой подход типичен для языка Elixir.
Люди упоминали, что коды возврата могут привести к тому, что у вас будет много вложенных
if
операторов, но с этим можно справиться с помощью лучшего синтаксиса. В Elixir этотwith
оператор позволяет легко отделить серию возвращаемых значений счастливого пути от любых ошибок.В Эликсире все еще есть функции, вызывающие исключения. Возвращаясь к моему первому примеру, я мог бы сделать любое из этих действий, чтобы вызвать исключение, если файл не может быть записан.
Если я, как вызывающий, знаю, что хочу вызвать ошибку в случае сбоя записи, я могу выбрать вызов
File.write!
вместоFile.write
. Или я могу позвонитьFile.write
и обработать каждую из возможных причин неудачи по-разному.Конечно, всегда можно сделать
rescue
исключение, если захотим. Но по сравнению с обработкой информативного возвращаемого значения мне это кажется неудобным. Если я знаю, что вызов функции может завершиться неудачей или даже не удастся, его отказ не является исключительным случаем.источник