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

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

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

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

Ответ 1

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

С++ основан на 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, если одна из его вызываемых функций не работает?

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

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

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

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

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

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

Набрано исключение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Заключение

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

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

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

Ответ 2

Я использую оба варианта.

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

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

Ответ 3

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

Возможно, это самая убедительная причина (стр. 179):

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

Ответ 4

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

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

Ответ 5

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

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

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

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

Ответ 6

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

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

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

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

Ответ 7

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

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

Ответ 8

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

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

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

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

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

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

Ответ 9

Коды возврата не проходят тестPit of Success для меня почти каждый раз.

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

Относительно производительности:

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

Ответ 10

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

Ответ 11

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

Ответ 12

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

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

Ответ 13

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

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

Ответ 14

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

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

Также; шаблон try-catch-finally - это анти-шаблон в моей книге. Try-finally может быть хорошим, try-catch также может быть хорошим, но try-catch - наконец-то никогда не бывает хорошим. try-finally часто может быть заменен на "использование" (шаблон 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-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 цента в любом случае!:)

Ответ 15

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

Ответ 16

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

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

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

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

Ответ 17

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

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

Ответ 18

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

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

Ответ 19

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

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

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

Ответ 20

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

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

Ответ 21

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

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

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

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

Ответ 22

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

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

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

Ответ 23

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

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