it-roy-ru.com

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

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

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

410
Konrad

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

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

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

318
Mitch Wheat

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

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

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

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

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

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

132
D.Shawley

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

46
sharptooth

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

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

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

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

24
Tadeusz Kopec

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

24
AshleysBrain

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

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

14
Kristopher Johnson

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

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

11
starblue

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

9
Donal Fellows

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

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

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

6
Bananeweizen

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

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

int f()
{
    // Do stuff

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

int condition = f();

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

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

5
Mike Bailey

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

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

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

2
bluedog

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

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

Я хотел бы добавить к этому обсуждению, что начиная с 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"
1
GPMueller

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

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

Экспоненциально, потому что если каждый метод разветвляется двумя различными способами, то каждый раз, когда вы вызываете другой метод, вы возводите в квадрат предыдущее число потенциальных результатов. К тому времени, как вы вызвали пять методов, вы получите до 256 возможных результатов как минимум. Сравните это с не выполнением try/catch в каждом методе, и у вас есть только один путь.

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

Короче говоря, попытка/ловля усложняет понимание кода.

0
user875234

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

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

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

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

0
user2502917