Почему бы мне не обернуть каждый блок в «try» - «catch»? - PullRequest
418 голосов
/ 29 апреля 2010

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

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

Ответы [ 16 ]

326 голосов
/ 29 апреля 2010

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

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

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

133 голосов
/ 29 апреля 2010

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

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

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

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

  3. "поймай, очисти, отбрось" : это одна из моих любимых мозолей. Если вы многое видите, то вам следует применить Resource Acquisition is Initialization методики и поместить часть очистки в деструктор экземпляра объекта janitor .

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

46 голосов
/ 29 апреля 2010

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

25 голосов
/ 29 апреля 2010

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

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

Во-вторых, сохранение отменяется всякий раз, когда выдается исключение. При каждом попытке перехвата под-метода, если генерируется исключение, вы попадаете в блок 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, интеллектуальных указателях и других объектах, которые освобождают свои ресурсы в деструкторах, поскольку объекты всегда уничтожаются до исключений.

25 голосов
/ 29 апреля 2010

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

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

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

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

14 голосов
/ 29 апреля 2010

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

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

11 голосов
/ 29 апреля 2010

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

9 голосов
/ 29 апреля 2010

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

6 голосов
/ 23 мая 2010

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

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

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

5 голосов
/ 29 апреля 2010

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

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

int f()
{
    // Do stuff

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

int condition = f();

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

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

...