Почему я не должен обертывать каждый блок в "try" - "catch"?

Я всегда считал, что если метод может генерировать исключение, то беззаботный не защищает этот вызов значимым блоком try.

Я только что написал "Вы должны ВСЕГДА переносить вызовы, которые могут бросаться в try, catch blocks". на этот вопрос, и ему сказали, что это "замечательно плохой совет" - я хотел бы понять, почему.

Ответ 1

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

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

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

Ответ 2

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

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

  • "catch, log, rethrow": если вы хотите вести регистрацию на основе области действия, тогда напишите класс, который выдает оператор журнала в своем деструкторе, когда стек разворачивается из-за исключения (ala std::uncaught_exception()). Все, что вам нужно сделать, это объявить экземпляр ведения журнала в интересующей вас области и, вуаля, у вас есть протоколирование и нет ненужной логики try/catch.

  • "catch, throw translation": это обычно указывает на проблему абстракции. Если вы не реализуете федеративное решение, в котором вы переводите несколько конкретных исключений в один общий, вы, вероятно, имеете ненужный слой абстракции... и не говорите, что "мне может понадобиться это завтра".

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

Я рассматриваю код, который усеян блоками try/catch, чтобы стать хорошей мишенью для проверки кода и рефакторинга. Это указывает на то, что обработка исключений не совсем понятна или код стал amβba и нуждается в рефакторинге.

Ответ 3

Потому что следующий вопрос: "Я поймал исключение, что мне делать дальше?" Что вы будете делать? Если вы ничего не сделаете - эта ошибка скрывается, и программа может "просто не работать" без каких-либо шансов найти то, что произошло. Вам нужно понять, что именно вы будете делать, как только поймаете это исключение, и поймаете только, если знаете.

Ответ 4

Herb Sutter написал об этой проблеме здесь. Конечно, стоит прочитать.
Тизер:

"Написание исключающего кода кода в основном заключается в написании" try "и" catch "в правильных местах". Обсудить.

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

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

Ответ 5

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

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

Два, сохранение отменяется всякий раз, когда генерируется исключение. При каждом под-методе try-catching, если генерируется исключение, вы попадаете в этот блок catch catch, выполнение оставляет функцию, и оно продолжается через SaveDocument(). Если что-то уже пошло не так, вы, вероятно, захотите остановиться там.

Три, все ваши под-методы могут предполагать, что каждый вызов преуспевает. Если вызов завершился неудачно, выполнение перейдет в блок catch, и последующий код никогда не будет выполнен. Это может сделать ваш код намного чище. Например, здесь с кодами ошибок:

int ret = SaveFirstSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveSecondSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

ret = SaveThirdSection();

if (ret == FAILED)
{
    /* some diagnostic */
    return;
}

Вот как это можно записать с исключениями:

// these throw if failed, caught in SaveDocument catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();

Теперь гораздо яснее, что происходит.

Замечание, что безопасный код исключения может быть более сложным для записи другими способами: вы не хотите утечки какой-либо памяти, если выбрано исключение. Убедитесь, что вы знаете о RAII, STL-контейнерах, интеллектуальных указателях и других объектах, которые освобождают свои ресурсы в деструкторах, поскольку объекты всегда уничтожаются перед исключениями.

Ответ 6

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

Например, в вопросе, который породил ваш вопрос, спрашивающий спрашивает, можно ли игнорировать исключения для lexical_cast от целого числа до строки. Такой актер никогда не должен терпеть неудачу. Если это не сработало, в программе что-то пошло не так. Что вы могли бы сделать, чтобы восстановиться в этой ситуации? Вероятно, лучше всего просто позволить программе умереть, так как она находится в состоянии, которому нельзя доверять. Поэтому несоблюдение исключения может быть самым безопасным делом.

Ответ 7

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

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

Ответ 8

Лучший совет, который я слышал, заключается в том, что вы должны только когда-либо перехватывать исключения в точках, где вы можете разумно что-то сделать с исключительным условием, и что "catch, log and release" не является хорошей стратегией (если это иногда неизбежно библиотеки).

Ответ 9

Совет, который дал мне однажды профессор компьютерных наук, был: "Использовать блоки Try и Catch только тогда, когда невозможно обработать ошибку, используя стандартные средства".

В качестве примера он сказал нам, что если в программе возникла серьезная проблема в том месте, где невозможно сделать что-то вроде:

int f()
{
    // Do stuff

    if (condition == false)
        return -1;
    return 0;
}

int condition = f();

if (f != 0)
{
    // handle error
}

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

Ответ 10

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

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

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

Ответ 11

Если вы хотите проверить результат каждой функции, используйте коды возврата.

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

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

Ответ 12

Помимо вышеупомянутого совета, лично я использую несколько try + catch + throw; по следующей причине:

  • На границе другого кодера я использую try + catch + throw в коде, написанном мною, до того, как исключение будет передано вызывающему, которое написано другими, это дает мне возможность узнать, какое условие ошибки произошло в моем кода, и это место намного ближе к коду, изначально генерирующему исключение, тем ближе, тем легче найти причину.
  • На границе модулей, хотя другой модуль может быть написан моим же человеком.
  • Learning + Debug, в этом случае я использую catch (...) в С++ и catch (Exception ex) в С#, для С++ стандартная библиотека не бросает слишком много исключений, поэтому этот случай встречается редко в С++, Но обычное место в С#, С# имеет огромную библиотеку и зрелую иерархию исключений, код библиотеки С# бросает тонны исключения, теоретически я (и вы) должен знать все исключения из вызываемой вами функции и знать причину/случай, почему эти исключения выбрасываются и умеют обрабатывать их (проезжать мимо или ловить и обрабатывать их на месте) изящно. К сожалению, на самом деле очень сложно все знать о потенциальных исключениях, прежде чем я напишу одну строку кода. Поэтому я улавливаю все и позволяю моему коду говорить вслух путем ведения журнала (в среде продукта)/assert (в среде разработки), когда действительно происходит какое-либо исключение. Таким образом, я постепенно добавляю код обработки исключений. Я знаю, что это хорошо, но на самом деле это работает для меня, и я не знаю лучшего способа решить эту проблему.

Ответ 13

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

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

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

Этот метод был использован для быстрой стабилизации багги-приложения в компании Fortune 500, разработанной 12 Devs в течение 2 лет. Используя это, я определил, исправил, построил тесты и развернул 3000 исправлений за 4 месяца, и в этом случае система больше не сообщала о каких-либо исключениях, поскольку все они были обработаны. Это означает, что исправление каждые 15 минут в среднем в течение 4 месяцев.

Ответ 14

Вам не нужно скрывать каждую часть кода внутри try-catch. Основное использование блока try-catch - обработка ошибок и получение ошибок/исключений в вашей программе. Некоторое использование try-catch -

  • Вы можете использовать этот блок, где хотите обработать исключение, или просто можете сказать, что блок написанного кода может вызывать исключение.
  • Если вы хотите уничтожить свои объекты сразу после их использования, вы можете использовать блок try-catch.