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


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

Я только что написал'вы всегда должны обернуть вызовы, которые могут бросить в try, catch блоков. ' to этот вопрос и мне сказали, что это был "удивительно плохой совет" - я хотел бы понять, почему.

15 400

15 ответов:

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

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

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

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

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

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

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

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

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

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

Херб Саттер писал об этой проблеме здесь. Наверняка стоит почитать.
Тизер:

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

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

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

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

это хорошо по трем причинам: это удобно, потому что у вас есть одно-единственное место, чтобы сообщить об ошибке:SaveDocument() блок 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's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();

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

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

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

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

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

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

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

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

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

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

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

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

int f()
{
    // Do stuff

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

int condition = f();

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

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

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

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

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

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

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

Я хотел бы добавить к этой дискуссии, что С C++11, это имеет большой смысл, пока каждый catch блок rethrows исключение до тех пор, пока он не может/должен быть обработан. Сюда данные, которые могут быть сгенерированы. Поэтому я считаю, что предыдущие мнения частично устарели.

использовать std::nested_exception и std::throw_with_nested

это описано на StackOverflow здесь и здесь как этого добиться.

поскольку вы можете сделать это с любым производным классом исключений, вы можете добавить много информации к такому обратному следу! Вы также можете взглянуть на мой MWE на GitHub, где след будет выглядеть примерно так:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

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

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

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

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

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

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