Аргументы за или против использования Try / Catch в качестве логических операторов [закрыто]


36

Я только что обнаружил прекрасный код в приложении нашей компании, в котором в качестве логических операторов используются блоки Try-Catch.
Это означает: «создайте некоторый код, если при этом выдается эта ошибка, сделайте этот код, но если при этом выдается эта ошибка, сделайте вместо этого эту третью вещь».
Он использует «И наконец» в качестве «другого» выражения, которое появляется.
Я знаю, что это неправильно по своей сути, но прежде чем начать драку, я надеялся на некоторые хорошо продуманные аргументы.
И, эй, если у вас есть аргументы ЗА использование Try-Catch таким образом, пожалуйста, сообщите.

Для тех, кто интересуется, язык C #, а рассматриваемый код содержит более 30 строк и ищет конкретные исключения, он не обрабатывает ВСЕ исключения.


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

15
@FrustratedWIthFormsDesigner: Это плохие рассуждения. Как насчет людей, которые не убеждены? А как насчет моего реального вопроса, где я специально спрашиваю о причинах, так как не могу сказать «почему», просто потому что знаю, что это неправильно.
Джеймс П. Райт

3
Мое мнение сильно зависит от того, что на самом деле делает код. Есть вещи, которые нельзя проверить заранее (или разрешить состязание при попытке, например, много файловых операций - в промежутке между проверкой и операцией, что-нибудь может произойти с файлом) и должны быть try'd. Не каждый исключительный случай, который требует исключения вообще, должен быть фатальным в этом конкретном случае. Итак, не могли бы вы сделать это более простым, одинаково или более надежным способом без использования исключений?

11
Я надеюсь, что они на самом деле не используют блок «finally» в качестве «другого», потому что блок «finally» всегда запускается после предыдущего кода независимо от генерируемых исключений.
jimreed

2
Какой язык они используют? Это прекрасно, скажем, в OCaml, где стандартная библиотека регулярно генерирует исключения. Обработка исключений там очень дешевая. Но это не будет так эффективно в CLI или JVM.
SK-logic

Ответы:


50

Обработка исключений, как правило, является дорогостоящим способом управления потоком (конечно, для C # и Java).

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

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

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

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


1
Хороший вопрос о производительности, хотя это также будет зависеть от специфики платформы.
FrustratedWithFormsDesigner

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

5
@delnan - Нет, не единственный недостаток.
Одед

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

4
«Обработка исключений имеет тенденцию быть дорогим способом управления потоком». Ложь для Питона.
С.Лотт

17

Я только что обнаружил прекрасный код в приложении нашей компании, в котором в качестве логических операторов используются блоки Try-Catch. Это означает: «создайте некоторый код, если при этом выдается эта ошибка, сделайте этот код, но если при этом выдается эта ошибка, сделайте вместо этого эту третью вещь». Он использует «И наконец» в качестве «другого» выражения, которое появляется. Я знаю, что это неправильно по своей сути ...

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

try { 
    return Optional.of(Long.valueOf(s)); 
} catch (NumberFormatException) { 
    return Optional.empty(); 
}

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

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

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


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

@James: звучит так, будто код будет в порядке, если вы извлечете несколько более мелких методов.
Кевин Клайн

1
Можно утверждать, что Java действительно может использовать что-то более похожее на C # TryParse. В C # этот код будет int i; if(long.TryParse(s, out i)) return i; else return null;. TryParse возвращает false, если строка не может быть проанализирована. Если он может быть проанализирован, он устанавливает выходной аргумент в результаты анализа и возвращает true. Никаких исключений не возникает (даже внутренне в TryParse), и особенно нет исключений, которые относятся к типу, который означает ошибку программиста, как FormatExceptionвсегда указывает .NET .
Кевин Кэткарт

1
@ Кевин Кэткарт, но почему так лучше? код, который перехватывает NumberFormatException, гораздо более очевиден для меня, чем проверка результата TryParse на ноль.
Уинстон Эверт

1
@ ChrisMiskowiec, я подозреваю, какой из них вы находите более понятным, в значительной степени зависит от того, к чему вы привыкли. Я обнаружил, что поймать исключение гораздо легче, чем проверять магические значения. Но это все субъективно. Объективно, я думаю, что мы должны предпочесть исключения, потому что они имеют нормальное значение по умолчанию (сбой программы), а не значение по умолчанию, которое скрывает ошибку (продолжайте, как будто ничего не произошло). Это правда, что .NET работает плохо с исключениями. Но это результат дизайна .NET, а не природы исключений. Если на .NET, да, вы должны это сделать. Но в принципе исключения лучше.
Уинстон Эверт

12

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

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

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

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

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

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


10

Я видел, как эта модель использовалась несколько раз.

Есть 2 основные проблемы:

  • Это очень дорого (создание объекта исключения, сбор стека вызовов и т. Д.). Некоторые компиляторы могут фактически оптимизировать его, но я бы не стал рассчитывать на это в этом случае, потому что исключения не предназначены для такого использования, поэтому вы не можете ожидать, что люди оптимизируют его.
  • Использование исключений для потока управления на самом деле является скачком, очень похожим на a goto. Прыжки считаются вредными по ряду причин. Их следует использовать, если все альтернативы имеют значительные недостатки. На самом деле, во всем моем коде я помню только 2 случая, когда переходы были явно лучшим решением.

2
Просто поместите это там, если / еще также считается прыжком. Разница в том, что это прыжок, который может произойти только в этом конкретном месте (и он читается намного легче), и вам не нужно беспокоиться об именах меток (что вам не нужно делать с try / catch либо , но по сравнению с goto). Но просто сказать: «это прыжок, что плохо», также говорит, что если / еще плохо, когда это не так.
Стернберг

1
@jsternbarg: Да, если / else на самом деле скомпилированы для переходов на уровне машины, но на уровне языка цель перехода - это следующий оператор, а в случае циклов это текущий оператор. Я бы сказал, что это больше шаг, чем прыжок;)
back2dos

9

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

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

Я видел случаи, когда быстрее было бы сделать что-то глупое, например таблицы запросов, которых там не было, чем выяснить, существует ли таблица в PHP + MySQL ( вопрос, где я проверяю это, на самом деле мой единственный принятый ответ с отрицательным голосом) ,

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

  1. Случайное глотание вложенных исключений. Это главное. Если вы поймете какое-то глубоко вложенное исключение, с которым пытается справиться кто-то другой, вы только что застрелили своего коллегу-программиста (возможно, даже вас!) В ногу.
  2. Тесты становятся неочевидными. Блок кода, в котором есть исключение, может иметь одну из нескольких ошибок. С другой стороны, логическое значение, хотя теоретически это может раздражать отладку, обычно это не так. Это особенно верно, поскольку try...catchсторонники потока управления обычно (по моему опыту) не следуют философии «минимизировать код в блоке попытки».
  3. Это не позволяет для else ifблока. Достаточно сказано (и если кто-то возражает с «Но они могут использовать разные классы исключений», мой ответ: «Иди в свою комнату и не выходи, пока не подумаешь о том, что сказал»).
  4. Это вводит в заблуждение грамматически. Exceptionдля остального мира (как не для приверженцев try...catchфилософии потока управления) означает, что что-то вошло в нестабильное (хотя, возможно, восстанавливаемое) состояние. Нестабильные состояния - ПЛОХО, и это должно держать нас всех ночью, если у нас действительно есть исключения, которых можно избежать (это на самом деле меня пугает, нет лжи).
  5. Это не соответствует общему стилю кодирования. Наша работа как с нашим кодом, так и с нашим пользовательским интерфейсом состоит в том, чтобы сделать мир как можно более очевидным. try...catchпоток управления идет вразрез с тем, что обычно считается наилучшей практикой. Это означает, что новичку в проекте потребуется больше времени, чтобы изучить проект, что означает увеличение количества человеко-часов без какой-либо выгоды.
  6. Это часто приводит к дублированию кода. Наконец, блоки, хотя это и не является строго обязательным, должны разрешать все висячие указатели, которые были оставлены открытыми прерванным блоком try. Так что попробуйте блоки. Это означает, что вы можете иметь try{ obj.openSomething(); /*something which causes exception*/ obj.doStuff(); obj.closeSomething();}catch(Exception e){obj.closeSomething();}. В более традиционном if...elseсценарии closeSomething()копирование и вставка менее вероятно (опять же, из личного опыта). (Следует признать, что этот конкретный аргумент больше связан с людьми, с которыми я встречался, чем с реальной философией).

AFAIK, # 6 - проблема только в Java, которая, в отличие от любого другого современного языка, не имеет ни замыканий, ни управления стеками. Это, конечно, не проблема, связанная с использованием исключений.
Кевин Клайн

4 и 5 кажутся мне одинаковыми. 3-6 не применяются, если ваш язык Python. 1 и 2 - потенциальные проблемы, но я думаю, что они решаются путем минимизации кода в блоке try.
Уинстон Эверт

1
@ Уинстон, я не согласен. 1 и 2 - это основные проблемы, которые, хотя и уменьшаются из-за лучших стандартов кодирования, все же заставляют задуматься, не лучше ли просто сначала избежать потенциальной ошибки. 3 применяется, если ваш язык Python. 4-5 также могут применяться для Python, но они не обязаны. 4 и 5 - это очень разные моменты - заблуждение отличается от стилистических различий. Вводящий в заблуждение код может делать что-то вроде названия триггера для запуска транспортного средства, «кнопка остановки». Стилистически, с другой стороны, это может быть аналогично тому, чтобы избегать отступов всех блоков в C.
cwallenpoole

3) В Python есть блок else для исключений. Это очень полезно для избежания проблем, которые вы обычно получаете за 1 и 2.
Уинстон Эверт

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

4

Мой главный аргумент в том, что использование try / catch для логики прерывает логический поток. Следование «логике» через нелогические конструкции является (для меня) нелогичным и запутанным. Я привык читать свою логику как «если условие, то A еще B». Чтение того же утверждения, что и «Попробуйте A catch, затем выполните B», кажется странным. Было бы еще более странно, если оператор A- это простое присваивание, требующее дополнительного кода для принудительного исключения, если conditionоно ложно (и выполнение этого, вероятно, в ifлюбом случае потребует -соглашения).


2

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

Я имею в виду, есть несколько возможностей:

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

... Я думаю, что все это одинаково вероятно. Так что просто спросите их приятно.


1

Try Catch следует использовать только для обработки исключений. Более того, специфическая обработка исключений. Ваш пробный улов должен отлавливать только ожидаемые исключения, в противном случае он сформирован неправильно. Если вам нужно использовать catch all try catch, то вы, вероятно, делаете что-то не так.

РЕДАКТИРОВАТЬ:

Причина: Вы можете утверждать, что если вы используете try catch в качестве условного оператора, как вы собираетесь учитывать РЕАЛЬНЫЕ исключения?


2
ОП просит причины / аргументы. Вы не предоставили ни одного.
Одед

«Вы можете утверждать, что если вы используете try catch в качестве условного оператора, как вы собираетесь учитывать РЕАЛЬНЫЕ исключения?» - Улавливание этих реальных исключений в catchпредложении, отличном от того, которое ловит «нереальные» исключения?

@delnan - Спасибо! Вы доказали точку, которую я собирался сделать. Если бы кто-то ответил на то, что вы только что сказали и на мою причину, и на Одеда, я бы просто перестал говорить, потому что он не ищет причин, по которым он просто упрям, и я не трачу свое время на упрямство.
AJC

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

1
Использование try-catch в качестве условного оператора никоим образом не мешает ему работать и для отлова «реальных» исключений.
Уинстон Эверт

1

Исключением являются случаи, когда происходят исключительные вещи. Исключительно ли функционирует программа в соответствии с обычным рабочим процессом?


1
Но почему? Суть вопроса в том, почему (или почему нет) исключения хороши только для этого.
Уинстон Эверт

Не всегда "исключительный". Например, отсутствие ключа в хеш-таблице не является чем-то исключительным, и, тем не менее, библиотека OCaml, как правило, вызывает исключение в таких случаях. И это нормально - обработка исключений там очень дешевая.
SK-logic

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

1

Причина использования исключений:

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

Причины не использовать исключения:

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

В конце:

Цель состоит в том, чтобы написать код, который сообщает, что происходит. Исключения могут помочь / помешать этому в зависимости от того, что делает код. Попробовать / поймать в Python для KeyError в справочнике словаря (если вы знаете язык) идеально.


0

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

Так что попробуйте План A, и если План A умирает, поймайте и запустите с Планом B ... А если план B потерпит неудачу, используйте, наконец, план Plan C, который либо исправит одну из неисправных служб в A или B, либо страницу меня.


0

Это зависит от языка и, возможно, от используемой реализации. Стандарт C ++ состоит в том, что исключения должны быть сохранены для действительно исключительных событий. С другой стороны, в Python одно из указаний гласит: « Проще просить прощения, чем разрешения », поэтому поощряется интеграция блоков try / исключением в логику программы.


«Стандарт в C ++ заключается в том, что исключения должны сохраняться для действительно исключительных событий». Это нонсенс. Даже изобретатель языка не согласен с вами .
arayq2

-1

Это плохая привычка, но я делаю это время от времени на питоне. Например, вместо того, чтобы проверить, существует ли файл, я просто пытаюсь удалить его. Если это вызывает исключение, я ловлю и просто продолжаю, зная, что его там нет. <== (не обязательно верно, если файл принадлежит другому пользователю, но я делаю это в домашнем каталоге пользователя, поэтому это НЕ ДОЛЖНО происходить).

Тем не менее, чрезмерное использование это плохая практика.


3
В этом примере использование исключений вместо «смотреть перед прыжком» на самом деле является хорошей идеей: это позволяет избежать условий гонки. Успешная проверка доступности файла (наличие, права доступа, отсутствие блокировки) не означает более позднего - даже если это будет несколько опкодов позже - доступ (открытие, удаление, перемещение, блокировка и т. Д.) Будет приостановлен, так как файл мог быть измененным (удаленным, перемещенным, изменили его разрешения) между ними.

Если python генерирует исключение только потому, что файл не существует, это, похоже, поощряет вредные привычки, как описывает Делнан.
Пер Йоханссон,

@delnan: Хотя это правда, ИМХО, если вы столкнетесь с такими условиями гонки, вам следует пересмотреть свой дизайн. В таком случае в ваших системах явно отсутствует разделение интересов. Если у вас нет веских причин для таких действий, вы должны либо унифицировать доступ на своем конце, либо использовать подходящую базу данных для обработки нескольких клиентов, пытающихся выполнить транзакции с одними и теми же постоянными данными.
back2dos

1
Это не плохая привычка. Это рекомендуемый стиль в Python.
Уинстон Эверт

@ back2dos, можете ли вы предотвратить это при работе с внешними ресурсами, такими как другие пользователи базы данных / файловой системы?
Уинстон Эверт

-1

Мне нравится, как Apple это определяет : исключения только для ошибок программистов и фатальных ошибок времени выполнения. Остальное используйте коды ошибок. Это помогает мне решить, когда его использовать, подумав про себя: «Это ошибка программиста?»


-1

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

По многим причинам, по которым логика goto плоха, вы можете обратиться к этому вопросу: https://stackoverflow.com/questions/46586/goto-still-considered-harmful

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.