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

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

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

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

26 ответов

Решение

Для некоторых языков (например, 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, который может возвращать bool "false" (для сбоя)
  • doSomethingElseSagain, который может возвращать объект Error (как с __LINE__, __FILE__, так и с половиной переменных стека).
  • doTryToDoSomethingWithAllThisMess, который, ну... Используйте вышеупомянутые функции и возвращают код ошибки типа...

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

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

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

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

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

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

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

Исключение набирается

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Это легко добавить к устаревшему коду. Например:

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.
}

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

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

Заключение

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

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

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

Я использую оба на самом деле.

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

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

В соответствии с главой 7 под названием "Исключения" в Руководстве по проектированию инфраструктуры: условные обозначения , идиомы и шаблоны для многократно используемых библиотек.NET приводятся многочисленные обоснования того, почему использование исключений над возвращаемыми значениями необходимо для сред OO, таких как C#.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Часто приятно иметь возможность использовать Исключение, чтобы указать "запрос не может быть выполнен", а не возвращать значение 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 в большем количестве случаев, чем каталог, которого не существует, но это "достаточно хорошее" решение для многих строго контролируемых ситуаций.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Такой подход типичен для языка 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операторов, но с этим можно справиться с помощью лучшего синтаксиса. В Эликсире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исключение, если мы хотим. Но по сравнению с обработкой информативного возвращаемого значения мне это кажется неудобным. Если я знаю, что вызов функции может завершиться неудачей или даже не удастся, его отказ не является исключительным случаем.

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

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

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

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

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

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 цента!:)

Есть несколько важных аспектов, которые до сих пор не упоминались в этом очень интересном обсуждении.

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

Второй момент совсем другой, он касается того, представляет ли язык размеченные союзы, и если да, то как. Вопрос был строго говоря о "кодах ошибок". Как и некоторые ответы, в которых упоминалось, что коды ошибок не могут передавать информацию так же хорошо, как исключения. Это верно, если код ошибки является числом. Но для более справедливого противопоставления исключениям, вероятно, следует рассматривать ошибочные значения типа размеченного объединения. Таким образом, возвращаемое значение вызываемого объекта будет иметь тип размеченного объединения, и это будет либо желаемое значение счастливого пути, либо полезная нагрузка, которую в противном случае имело бы исключение.. Насколько часто этот подход оказывается достаточно элегантным, чтобы его предпочесть, зависит от языка программирования. Например, в F# есть очень элегантные размеченные объединения и соответствующее сопоставление с образцом. В таком языке было бы более соблазнительно избегать исключений, чем, скажем, в C++.

Третий и последний пункт касается функционального программирования и чистых функций. Исключениями являются (на практике и в теоретико-информатике) «побочные эффекты». Другими словами, функции или методы, работающие с исключениями, не являются чистыми. (Одним из практических следствий является то, что при работе с исключениями необходимо обращать внимание на порядок вычислений.) Напротив, значения ошибок являются чистыми, поскольку являются обычными возвращаемыми значениями без каких-либо побочных эффектов. Следовательно, функциональные программисты могут с большей вероятностью не одобрять исключения, чем объектно-ориентированные программисты. (В частности, если язык также имеет элегантное представление вышеупомянутых разграниченных союзов.)

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

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

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

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

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?
  }
}

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

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

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

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

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

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

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

Я предпочитаю исключения кодам возврата.

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

  • Если ошибки в передаются через коды возврата (коды ошибок),

    • во время компиляции я не буду предупрежден

    • если я запускаю код

      • если ошибка не происходит, я не замечаю свою ошибку

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

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

  • Если ошибки передаются через выбрасываемые исключения,

    • во время компиляции я не буду предупрежден

    • если я запускаю код

      • если ошибка не происходит, я не замечаю свою ошибку

      • если ошибка произойдет, я уверен, что замечу свою ошибку

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

Однако исключения не идеальны. Есть как минимум две критические проблемы:

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

  2. Трудно определить все исключения, которые могут генерироваться, особенно когда foo вызывает другие функции (которые также могут генерировать исключения).

Функция в Java, «проверенные исключения», решает обе проблемы.

В Java при определении функции я должен явно указать, какие исключения она может генерировать, используяключевое слово. Пример:

      private static void foo() throws FileNotFoundException {
    File file = new File("not_existing_file.txt");
    FileInputStream stream = new FileInputStream(file);
}

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

Другие вопросы по тегам