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

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

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

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

    Для некоторых языков (например, 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)) ; 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    В моей работе мы используем нечто вроде «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. } 

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

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

    Вывод

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Почему тогда попытка-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 цента! 🙂

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

     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.

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

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

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

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

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

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

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

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

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

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

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

    Interesting Posts

    Коллекции. Синхронизированный список и синхронизация

    Определение int64_t

    SFINAE работает в обратном типе, но не как параметр шаблона

    Является ли # прагма когда-то безопасным включать охрану?

    Обновление Windows KB2952664 (Compattelrunner.exe) не может быть удалено из Windows 7

    «Npm install» устанавливает все зависимости в каталоге node_modules, вместо того, чтобы иметь вложенные

    Преобразование метки времени ISO 8601 в NSDate: как вы работаете с временным смещением UTC?

    Использование для множественного наследования?

    Почему я не могу использовать метод __cmp__ в Python 3 как для Python 2?

    Как удалить элементы сценария до их выполнения?

    Какая наилучшая реализация для АОП в .Net?

    Есть ли использование unique_ptr с массивом?

    Когда нам нужны фигурные скобки вокруг переменных оболочки?

    Как запустить приложение как можно раньше при входе пользователя в Windows 8

    Как сделать мой USB-накопитель загрузочным

    Давайте будем гением компьютера.