Какие и почему вы предпочитаете исключения или коды возврата?


92

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

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

обновление: спасибо за все ответы! Я должен сказать, что хотя мне не нравится непредсказуемость потока кода с исключениями. Ответ о коде возврата (и ручках их старшего брата) действительно добавляет к коду много шума.


1
Проблема курицы или яйца из мира программного обеспечения ... вечно спорная. :)
Gishu

Извините за это, но, надеюсь, наличие разнообразных мнений поможет людям (в том числе и мне) сделать правильный выбор.
Роберт Гулд,

Ответы:


107

Для некоторых языков (например, C ++) утечка ресурсов не должна быть причиной

C ++ основан на RAII.

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

Коды возврата более подробны

Они многословны и имеют тенденцию развиваться во что-то вроде:

if(doSomething())
{
   if(doSomethingElse())
   {
      if(doSomethingElseAgain())
      {
          // etc.
      }
      else
      {
         // react to failure of doSomethingElseAgain
      }
   }
   else
   {
      // react to failure of doSomethingElse
   }
}
else
{
   // react to failure of doSomething
}

В конце концов, ваш код представляет собой набор идентифицированных инструкций (я видел такой код в производственном коде).

Этот код можно было бы перевести на:

try
{
   doSomething() ;
   doSomethingElse() ;
   doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
   // react to failure of doSomething
}
catch(const SomethingElseException & e)
{
   // react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
   // react to failure of doSomethingElseAgain
}

Которая четко разделяет код и обработку ошибок, что может быть хорошо .

Коды возврата более хрупкие

Если не какое-то непонятное предупреждение от одного компилятора (см. Комментарий "phjr"), их можно легко проигнорировать.

В приведенных выше примерах предположим, что кто-то забыл обработать свою возможную ошибку (это происходит ...). Ошибка игнорируется при "возврате" и, возможно, позже взорвется (например, указатель NULL). Такой же проблемы за исключением не будет.

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

Коды возврата иногда необходимо переводить

Допустим, у нас есть следующие функции:

  • doSomething, который может возвращать int с именем NOT_FOUND_ERROR
  • doSomethingElse, который может вернуть логическое значение false (для ошибки)
  • doSomethingElseAgain, который может возвращать объект Error (с переменными __LINE__, __FILE__ и половиной переменных стека.
  • doTryToDoSomethingWithAllThisMess, который, ну ... Используйте вышеуказанные функции и возвращайте код ошибки типа ...

Каков тип возврата doTryToDoSomethingWithAllThisMess, если одна из его вызываемых функций не работает?

Коды возврата - не универсальное решение

Операторы не могут вернуть код ошибки. Конструкторы C ++ тоже не могут.

Коды возврата означают, что вы не можете связывать выражения

Следствие из вышеизложенного. Что, если я хочу написать:

CMyType o = add(a, multiply(b, c)) ;

Я не могу, потому что возвращаемое значение уже используется (а иногда его нельзя изменить). Таким образом, возвращаемое значение становится первым параметром, отправленным как ссылка ... Или нет.

Тип исключения

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

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

Никогда не используйте catch (...) без повторного броска

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

Исключение составляют ... NUKE

Проблема с исключением состоит в том, что их чрезмерное использование приведет к созданию кода, полного попыток / уловок. Но проблема в другом: кто пытается / поймать свой код с помощью контейнера STL? Тем не менее, эти контейнеры могут отправлять исключение.

Конечно, в C ++ никогда не позволяйте исключению выйти из деструктора.

Исключение составляют ... синхронные

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

Решением может быть их смешивание?

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

Итак, единственный вопрос - «чего не должно происходить?»

Это зависит от контракта вашей функции. Если функция принимает указатель, но указывает, что указатель должен быть отличным от NULL, тогда нормально генерировать исключение, когда пользователь отправляет NULL указатель (вопрос в том, что в C ++, когда автор функции не использовал ссылки вместо этого указателей, но ...)

Другое решение - показать ошибку

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

В своей работе мы используем своего рода «Утверждение». В зависимости от значений файла конфигурации независимо от параметров компиляции отладки / выпуска:

  • записать ошибку
  • откройте окно сообщения с надписью "Привет, у вас проблема"
  • откройте окно сообщения с надписью «Привет, у вас проблема, вы хотите отладить»

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

Его легко добавить в унаследованный код. Например:

void doSomething(CMyObject * p, int iRandomData)
{
   // etc.
}

приводит своего рода код, похожий на:

void doSomething(CMyObject * p, int iRandomData)
{
   if(iRandomData < 32)
   {
      MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
      return ;
   }

   if(p == NULL)
   {
      MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
      throw std::some_exception() ;
   }

   if(! p.is Ok())
   {
      MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
   }

   // etc.
}

(У меня есть похожие макросы, которые активны только при отладке).

Обратите внимание, что на производстве файл конфигурации не существует, поэтому клиент никогда не видит результат этого макроса ... Но его легко активировать, когда это необходимо.

Вывод

Когда вы кодируете с использованием кодов возврата, вы готовитесь к неудаче и надеетесь, что ваша крепость тестов достаточно безопасна.

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

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


3
Да, НО относительно вашего первого примера, это можно легко записать как: if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
bobobobo

Почему бы и нет ... Но я все равно считаю, что doSomething(); doSomethingElse(); ...лучше, потому что если мне нужно добавить if / while / etc. операторы для нормального выполнения, я не хочу, чтобы они смешивались с if / while / etc. операторы, добавленные для исключительных целей ... И поскольку реальное правило использования исключений - это выбросить , а не поймать , операторы try / catch обычно не являются инвазивными.
paercebal

1
Ваш первый пункт показывает, в чем проблема с исключениями. Ваш поток управления становится странным и отделен от реальной проблемы. Он заменяет некоторые уровни идентификации каскадом уловов. Я бы использовал оба кода возврата (или возвращал объекты с тяжелой информацией) для возможных ошибок и исключений для вещей, которых не ожидали .
Питер

2
@ Питер Вебер: Это не странно. Он отделен от реальной проблемы, поскольку не является частью нормального потока выполнения. Это исключительное исполнение. И опять же, суть исключения состоит в том, чтобы в случае исключительной ошибки часто генерировать и ловить редко , если вообще когда-либо. Поэтому блоки catch даже редко появляются в коде.
paercebal 03

Примеры такого рода дебатов очень упрощены. Обычно doSomething () или doSomethingElse () на самом деле что-то выполняют, например, изменяют состояние какого-либо объекта. Код исключения не гарантирует возврата объекта в предыдущее состояние и даже меньше, когда ловушка очень далеко от выброса ... В качестве примера представьте, что doSomething вызывается дважды и увеличивает счетчик перед выбросом. Как при перехвате исключения узнать, что нужно уменьшить один или два раза? В общем, писать безопасный код для всего, что не является игрушечным примером, очень сложно (невозможно?).
xryl669

36

На самом деле я использую оба.

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

Исключения используются исключительно для вещей, которых я НЕ жду.


+1 за очень простой путь. По крайней мере, он достаточно короткий, чтобы я мог очень быстро его прочитать. :-)
Ашиш Гупта

1
« Исключения используются исключительно для вещей, которых я НЕ ожидаю». Если вы их не ожидаете, тогда зачем и как вы можете их использовать?
анар халилов 04

5
То, что я этого не ожидаю, не означает, что я не понимаю, как это могло произойти. Я ожидаю, что мой SQL Server будет включен и отвечает. Но я все еще кодирую свои ожидания, чтобы в случае непредвиденного простоя я мог изящно потерпеть неудачу.
Стивен Райтон

Разве не отвечающий SQL Server легко отнести к категории «известная возможная ошибка»?
tkburbidge,

21

Согласно главе 7, озаглавленной «Исключения» в документе Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries , дается множество объяснений того, почему использование исключений вместо возвращаемых значений необходимо для объектно-ориентированных структур, таких как C #.

Возможно, это самая веская причина (стр. 179):

«Исключения хорошо интегрируются с объектно-ориентированными языками. Объектно-ориентированные языки, как правило, накладывают ограничения на сигнатуры членов, которые не налагаются функциями на языках, отличных от объектно-ориентированных. Например, в случае конструкторов, перегрузок операторов и свойств разработчик не имеет выбора в возвращаемом значении. По этой причине невозможно стандартизировать создание отчетов об ошибках на основе возвращаемых значений для объектно-ориентированных сред. Метод сообщения об ошибках, например исключения, который выходит за рамки сигнатуры метода это единственный вариант ".


10

Я предпочитаю (в C ++ и Python) использовать исключения. Предоставляемые на языке средства делают четко определенным процесс как для вызова, так и для перехвата и (при необходимости) повторного вызова исключений, что упрощает просмотр и использование модели. По идее, он чище, чем коды возврата, поскольку конкретные исключения могут определяться по их именам и иметь дополнительную информацию, сопровождающую их. С кодом возврата вы ограничены только значением ошибки (если вы не хотите определить объект ReturnStatus или что-то еще).

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


2
Помните, что использование исключений затрудняет анализ программы.
Paweł Hajdan

7

Исключения следует возвращать только тогда, когда происходит что-то, чего вы не ожидали.

Другой момент исключения, исторически, заключается в том, что коды возврата по своей сути являются проприетарными, иногда 0 может быть возвращен из функции C, чтобы указать на успех, иногда -1, или любой из них в случае неудачи с 1 для успеха. Даже когда они перечисляются, перечисления могут быть неоднозначными.

Исключения также могут предоставить гораздо больше информации и, в частности, хорошо разобрать: «Что-то пошло не так, вот что, трассировка стека и некоторая вспомогательная информация для контекста»

При этом хорошо пронумерованный код возврата может быть полезен для известного набора результатов, простого «здесь n результатов функции, и она просто выполнялась таким образом»


6

В Java я использую (в следующем порядке):

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

  2. Возврат кодов ошибок во время обработки работы (и выполнение отката при необходимости).

  3. Исключения, но они используются только для неожиданных вещей.


1
Не было бы правильнее использовать утверждения для контрактов? Если контракт разорван, спасать нечего.
Paweł Hajdan

@ PawełHajdan, я полагаю, утверждения по умолчанию отключены. Это та же проблема, что и у C, assertв том, что он не обнаружит проблем в производственном коде, если вы не запускаете все время с утверждениями. Я склонен рассматривать утверждения как способ отловить проблемы во время разработки, но только для вещей, которые будут утверждать или не утверждать последовательно (например, вещи с константами, а не вещи с переменными или что-то еще, что может измениться во время выполнения).
paxdiablo

И двенадцать лет, чтобы ответить на ваш вопрос. Мне нужно запустить службу поддержки :-)
paxdiablo

6

Коды возврата почти каждый раз не проходят тест « Яма успеха ».

  • Слишком легко забыть проверить код возврата, а позже получить отвлекающую ошибку.
  • Коды возврата не содержат важной отладочной информации, такой как стек вызовов, внутренние исключения.
  • Коды возврата не распространяются, что, наряду с указанным выше, имеет тенденцию приводить к чрезмерному и переплетенному диагностическому журналу вместо ведения журнала в одном централизованном месте (обработчики исключений на уровне приложения и потока).
  • Коды возврата имеют тенденцию приводить к беспорядку в виде вложенных блоков if.
  • Время разработчика, потраченное на отладку неизвестной проблемы, которая в противном случае была бы очевидным исключением (яма успеха), ДОЛЖНО ДОРОГО.
  • Если бы команда, стоящая за C #, не намеревалась использовать исключения для управления потоком управления, исключения не вводились бы, не было бы фильтра «когда» в операторах catch и не было бы необходимости в операторе throw без параметров. .

Что касается производительности:

  • Исключения могут быть дорогостоящими в вычислительном отношении ОТНОСИТЕЛЬНО того, что не выбрасывать вообще, но они не зря называются ИСКЛЮЧЕНИЯМИ. При сравнении скорости всегда удается предположить 100% -ную частоту исключений, чего никогда не должно быть. Даже если исключение происходит в 100 раз медленнее, какое это имеет значение, если оно происходит только в 1% случаев?
  • Если мы не говорим об арифметике с плавающей запятой для графических приложений или о чем-то подобном, циклы ЦП обходятся дешевле, чем время разработчика.
  • Стоимость с точки зрения времени - тот же аргумент. По сравнению с запросами к базе данных, вызовами веб-сервисов или загрузкой файлов нормальное время приложения будет меньше времени исключения. Исключения составили почти менее МИКРОсекунды в 2006 г.
    • Я осмелюсь любому, кто работает в .net, настроить ваш отладчик на прерывание всех исключений и отключение только моего кода и посмотреть, сколько исключений уже происходит, о которых вы даже не знаете.

4

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


4
Вы неправильно это понимаете. Они имели в виду, что «если ваша программа выдает исключения в своих обычных потоках, это неправильно». Другими словами, «используйте исключения только в исключительных случаях».
Paweł Hajdan

4

Я написал об этом в блоге некоторое время назад.

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


Связанное сообщение в блоге больше о том, как посмотреть, прежде чем прыгать (проверки), а не о том, что проще просить прощения, чем разрешения (исключения или коды возврата). Я ответил там своими мыслями по этому поводу (подсказка: TOCTTOU). Но этот вопрос касается другого вопроса, а именно, при каких условиях использовать механизм исключений языка, а не возвращать значение со специальными свойствами.
Damian Yerrick

Я полностью согласен. Кажется, я кое-чему научился за последние девять лет;)
Thomas

4

Мне не нравятся коды возврата, потому что они вызывают появление в вашем коде следующего шаблона

CRetType obReturn = CODE_SUCCESS;
obReturn = CallMyFunctionWhichReturnsCodes();
if (obReturn == CODE_BLOW_UP)
{
  // bail out
  goto FunctionExit;
}

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

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

Если у вас есть несколько состояний после вызова метода, которые должны обрабатываться по-другому (и не являются исключительными случаями), используйте коды ошибок или параметры out. Хотя лично я обнаружил, что это бывает редко ..

Я немного искал контраргумент о «потере производительности» ... больше в мире C ++ / COM, но в новых языках, я думаю, разница не так уж велика. В любом случае, когда что-то взрывается, проблемы с производительностью уходят на второй план :)


3

Для любого достойного компилятора или среды выполнения исключения не влекут за собой значительных штрафов. Это более или менее похоже на оператор GOTO, который переходит к обработчику исключения. Кроме того, наличие исключений, обнаруженных средой выполнения (например, JVM), помогает намного проще изолировать и исправить ошибку. Я приму исключение NullPointerException в Java вместо segfault в C в любой день.


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

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

Стек вызовов может стать чрезвычайно сложным во время выполнения, и компиляторы обычно не проводят такого анализа. Даже если бы они это сделали, вам все равно придется пройтись по стеку, чтобы найти след. Вам также все равно придется раскручивать стек, чтобы иметь дело с finallyблоками и деструкторами для объектов, выделенных стеком.
Дерек Парк

Я согласен с тем, что отладочные преимущества исключений часто компенсируют затраты на производительность.
Дерек Парк

1
Дерак Парк, исключения дороги, когда они случаются. По этой причине не следует злоупотреблять ими. Но когда этого не происходит, они практически ничего не стоят.
paercebal

3

У меня есть простой набор правил:

1) Используйте коды возврата для вещей, на которые вы ожидаете реакции вашего непосредственного вызывающего абонента.

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

В Java я когда-либо использовал только непроверенные исключения, проверенные исключения в конечном итоге были просто еще одной формой кода возврата, и, по моему опыту, двойственность того, что могло быть «возвращено» вызовом метода, обычно была скорее препятствием, чем помощью.


3

Я использую исключения в python как в исключительных, так и в неисключительных обстоятельствах.

Часто приятно иметь возможность использовать Exception, чтобы указать, что «запрос не может быть выполнен», в отличие от возврата значения Error. Это означает, что вы / всегда / знаете, что возвращаемое значение имеет правильный тип, а не произвольно None или NotFoundSingleton или что-то в этом роде. Вот хороший пример того, где я предпочитаю использовать обработчик исключений вместо условия для возвращаемого значения.

try:
    dataobj = datastore.fetch(obj_id)
except LookupError:
    # could not find object, create it.
    dataobj = datastore.create(....)

Побочный эффект заключается в том, что при запуске datastore.fetch (obj_id) вам никогда не нужно проверять, является ли его возвращаемое значение None, вы получите эту ошибку немедленно бесплатно. Это противоречит аргументу «ваша программа должна иметь возможность выполнять все свои основные функции без использования исключений».

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

# wrong way:
if os.path.exists(directory_to_remove):
    # race condition is here.
    os.path.rmdir(directory_to_remove)

# right way:
try: 
    os.path.rmdir(directory_to_remove)
except OSError:
    # directory didn't exist, good.
    pass

Один системный вызов вместо двух, состояния гонки нет. Это плохой пример, потому что, очевидно, это приведет к ошибке OSError в большем количестве обстоятельств, чем каталог не существует, но это «достаточно хорошее» решение для многих строго контролируемых ситуаций.


Второй пример вводит в заблуждение. Предположительно неправильный способ неверен, потому что код os.path.rmdir предназначен для создания исключения. Правильная реализация в потоке кода возврата будет выглядеть следующим образом: 'if rmdir (...) == FAILED: pass'
MaR

3

Я считаю, что коды возврата добавляют к шуму кода. Например, мне всегда не нравился внешний вид кода COM / ATL из-за кодов возврата. Для каждой строки кода должна быть проверка HRESULT. Я считаю, что код возврата ошибки - одно из плохих решений, принятых архитекторами COM. Это затрудняет логическую группировку кода, поэтому становится трудным проверка кода.

Я не уверен в сравнении производительности, когда в каждой строке явно проверяется код возврата.


1
COM wad предназначен для использования языками, которые не поддерживают исключения.
Кевин

Это хороший момент. Имеет смысл разобраться с кодами ошибок для языков сценариев. По крайней мере, VB6 хорошо скрывает детали кода ошибки, инкапсулируя их в объект Err, что несколько помогает в более чистом коде.
rpattabi

Я не согласен: VB6 регистрирует только последнюю ошибку. В сочетании с пресловутой «следующей ошибкой возобновить работу» вы вообще упустите источник своей проблемы к тому времени, когда увидите его. Обратите внимание, что это основа обработки ошибок в Win32 APII (см. Функцию GetLastError)
paercebal

2

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


2

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

Коды возврата ошибок, если они используются интенсивно, также могут вызвать очень уродливый код с большим количеством тестов if, аналогичных этой форме:

if(function(call) != ERROR_CODE) {
    do_right_thing();
}
else {
    handle_error();
}

Лично я предпочитаю использовать исключения для ошибок, которые ДОЛЖНЫ или ДОЛЖНЫ быть обработаны вызывающим кодом, и использовать коды ошибок только для «ожидаемых сбоев», когда возврат чего-либо действительно действителен и возможен.


По крайней мере, в C / C ++ и gcc вы можете дать функции атрибут, который будет генерировать предупреждение, если его возвращаемое значение игнорируется.
Павел Хайдан,

phjr: Хотя я не согласен с шаблоном «код ошибки возврата», ваш комментарий, возможно, должен стать полным ответом. Я нахожу это достаточно интересным. По крайней мере, это дало мне полезную информацию.
paercebal

2

Есть много причин предпочесть Исключения коду возврата:

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

2

Исключения не для обработки ошибок, ИМО. Исключения только что; исключительные события, которых вы не ожидали. Я говорю: используйте с осторожностью.

Коды ошибок могут быть в порядке, но возвращение 404 или 200 из метода - это плохо, ИМО. Вместо этого используйте перечисления (.Net), что сделает код более читаемым и более простым в использовании для других разработчиков. Также вам не нужно вести таблицу с числами и описаниями.

Также; паттерн «попробуй-поймай-наконец» - это антипаттерн в моей книге. Попробовать наконец-то может быть хорошо, пробовать-поймать тоже можно, но пробовать-наконец-то никогда не бывает. try-finally часто можно заменить оператором "using" (шаблон IDispose), что лучше IMO. И попытаться поймать, где вы действительно поймаете исключение, которое вы можете обработать, хорошо, или если вы сделаете это:

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

Итак, пока вы позволяете исключению продолжать пузыриться, все в порядке. Другой пример:

try{
    dbHasBeenUpdated = db.UpdateAll(somevalue); // true/false
}
catch (ConnectionException ex) {
    logger.Exception(ex, "Connection failed");
    dbHasBeenUpdated = false;
}

Здесь я действительно обрабатываю исключение; то, что я делаю за пределами try-catch, когда метод обновления терпит неудачу, - это другая история, но я думаю, что моя точка зрения была высказана. :)

Почему же тогда попытка-поймать-наконец - это анти-паттерн? Вот почему:

try{
    db.UpdateAll(somevalue);
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}
finally {
    db.Close();
}

Что произойдет, если объект db уже закрыт? Выдается новое исключение, которое необходимо обработать! Это лучше:

try{
    using(IDatabase db = DatabaseFactory.CreateDatabase()) {
        db.UpdateAll(somevalue);
    }
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

Или, если объект db не реализует IDisposable, сделайте следующее:

try{
    try {
        IDatabase db = DatabaseFactory.CreateDatabase();
        db.UpdateAll(somevalue);
    }
    finally{
        db.Close();
    }
}
catch (DatabaseAlreadyClosedException dbClosedEx) {
    logger.Exception(dbClosedEx, "Database connection was closed already.");
}
catch (Exception ex) {
    logger.Exception(ex, "UpdateAll method failed");
    throw;
}

В любом случае это мои 2 цента! :)


Будет странно, если у объекта есть .Close (), но нет .Dispose ()
абатищев

Я использую Close () только в качестве примера. Не стесняйтесь думать об этом как о чем-то другом. Как я утверждаю; следует использовать шаблон using (нет!), если он доступен. Это, конечно, означает, что класс реализует IDisposable, и поэтому вы можете вызвать Dispose.
noocyte

1

Я использую только исключения, без кодов возврата. Я говорю о Java здесь.

Общее правило, doFoo()которому я следую: если у меня вызывается метод, то из этого следует, что если он не выполняет «foo», как это было, то произошло что-то исключительное и должно быть сгенерировано Exception.


1

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

void foo()
{
  MyPointer* p = NULL;
  try{
    p = new PointedStuff();
    //I'm a module user and  I'm doing stuff that might throw or not

  }
  catch(...)
  {
    //should I delete the pointer?
  }
}

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


Для этого предназначен оператор <code> finally </code>. Но, увы, этого нет в стандарте C ++ ...
Томас

В C ++ вы должны придерживаться практического правила «Получать ресурсы в конструкторе и освобождать их в деструкторе. В этом конкретном случае auto_ptr будет работать как раз идеально.
Серж

Томас, ты ошибаешься. В C ++ нет, наконец, потому что он ему не нужен. Вместо него используется RAII. Решение Serge - это одно решение, использующее RAII.
paercebal

Роберт, используйте решение Сержа, и ваша проблема исчезнет. Теперь, если вы напишете больше try / catch, чем throw, тогда (судя по вашему комментарию), возможно, у вас есть проблема в вашем коде. Конечно, использование catch (...) без повторного броска обычно плохо, поскольку оно скрывает ошибку, чтобы лучше ее игнорировать.
paercebal

1

Мое общее правило в аргументе исключения и кода возврата:

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

Нет причин, по которым вы не могли бы использовать исключение при выполнении l10n / i18n. Исключения также могут содержать локализованную информацию.
штуковина

1

Я не считаю, что коды возврата менее уродливы, чем исключения. За исключением, у вас есть try{} catch() {} finally {}где, как и с кодами возврата, у вас естьif(){} . Раньше я боялся исключений по причинам, указанным в посте; вы не знаете, нужно ли очистить указатель, что у вас есть. Но я думаю, что у вас те же проблемы, когда дело касается кодов возврата. Вы не знаете состояние параметров, если не знаете некоторых подробностей о рассматриваемой функции / методе.

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

Мне нравится идея возврата значения (перечисления?) Для результатов и исключения в исключительном случае.


0

Для такого языка, как Java, я бы выбрал Exception, потому что компилятор выдает ошибку времени компиляции, если исключения не обрабатываются. Это заставляет вызывающую функцию обрабатывать / генерировать исключения.

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


0

Я обычно предпочитаю коды возврата, потому что они позволяют вызывающей стороне решить, является ли сбой исключительным .

Такой подход типичен для языка Elixir.

# I care whether this succeeds. If it doesn't return :ok, raise an exception.
:ok = File.write(path, content)

# I don't care whether this succeeds. Don't check the return value.
File.write(path, content)

# This had better not succeed - the path should be read-only to me.
# If I get anything other than this error, raise an exception.
{:error, :erofs} = File.write(path, content)

# I want this to succeed but I can handle its failure
case File.write(path, content) do
  :ok => handle_success()
  error => handle_error(error)
end

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

with {:ok, content} <- get_content(),
  :ok <- File.write(path, content) do
    IO.puts "everything worked, happy path code goes here"
else
  # Here we can use a single catch-all failure clause
  # or match every kind of failure individually
  # or match subsets of them however we like
  _some_error => IO.puts "one of those steps failed"
  _other_error => IO.puts "one of those steps failed"
end

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

# Raises a generic MatchError because the return value isn't :ok
:ok = File.write(path, content)

# Raises a File.Error with a descriptive error message - eg, saying
# that the file is read-only
File.write!(path, content)

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

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

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