Почему исключения должны использоваться консервативно?

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

78
задан Cœur 9 July 2018 в 17:02
поделиться

28 ответов

Я прочитал некоторые из ответов здесь. Я все еще поражен тем, к чему вся эта путаница. Я категорически не согласен со всем этим кодом исключений==spagetty. Под путаницей я подразумеваю, что есть люди, которые не ценят обработку исключений в C++. Я не знаю точно, как я узнал об обработке исключений в C++ - но я понял ее значение в течение нескольких минут. Это было примерно в 1996 году, и я использовал компилятор Borland C++ для OS/2. У меня никогда не было проблем с решением, когда использовать исключения. Я обычно оборачиваю ошибочные действия "сделай-делай" в классы C++. К таким действиям относятся:

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

Кроме того, существуют функциональные обертки. Чтобы обернуть системные вызовы (которые не попадают в первую категорию) в C++. Например, чтение/запись из/в файл. Если что-то не получается, будет выброшено исключение, которое содержит полную информацию об ошибке.

Затем существует перехват/перерастание исключений для добавления дополнительной информации о неудаче.

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

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

5
ответ дан 24 November 2019 в 10:23
поделиться

Я использую исключения, если:

  • произошла ошибка, которая не может быть восстановлен локально И
  • , если ошибка не восстанавливается из программы, должен завершиться.

Если ошибка может быть восстановлена ​​из (пользователь ввел «яблоко» вместо числа), то восстановите (запросите ввод снова , изменить значение по умолчанию и т. д.).

0
ответ дан 24 November 2019 в 10:23
поделиться

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

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

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

0
ответ дан 24 November 2019 в 10:23
поделиться

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

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

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

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

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

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

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

Почему неразумно использовать их для потока управления?

Потому что исключения нарушают нормальный «поток управления». Вы вызываете исключение, и нормальное выполнение программы прекращается, что может привести к тому, что объекты будут в несогласованном состоянии, а некоторые открытые ресурсы останутся неиспользованными. Конечно, в C # есть оператор using, который гарантирует, что объект будет удален, даже если из тела using возникнет исключение. Но давайте отвлечемся на время от языка. Предположим, фреймворк не будет размещать объекты за вас. Вы делаете это вручную. У вас есть система для запроса и освобождения ресурсов и памяти. У вас есть общесистемное соглашение о том, кто и в каких ситуациях отвечает за освобождение объектов и ресурсов. У вас есть правила работы с внешними библиотеками. Он отлично работает, если программа следует нормальному рабочему процессу. Но внезапно в середине выполнения вы вызываете исключение. Половина ресурсов осталась неиспользованной. Половина еще не запрошена. Если операция должна была быть транзакционной, сейчас она прервана. Ваши правила обработки ресурсов не будут работать, потому что те части кода, которые отвечают за освобождение ресурсов, просто не будут выполняться. Если кто-то еще захочет использовать эти ресурсы, он может найти их в несогласованном состоянии и также выйдет из строя, потому что они не могут предсказать эту конкретную ситуацию.

Скажем, вы хотели, чтобы метод M () вызывал метод N () для выполнения некоторой работы и организовать некоторый ресурс, затем вернуть его обратно в M (), который будет его использовать, а затем утилизирует. Хорошо. Теперь что-то идет не так в N (), и возникает исключение, которого вы не сделали. Не ожидайте в M (), поэтому исключение пузырится вверх, пока оно, возможно, не попадет в какой-то метод C (), который не будет знать, что происходит в глубине N (), и нужно ли и как освободить некоторые ресурсы.

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

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

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

90
ответ дан 24 November 2019 в 10:23
поделиться

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

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

Я сосредоточусь на другом моменте:

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

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

Так что вы закончите, элегантно прожигая свой процессор, не зная об этом.

Итак: используйте исключения только в исключительных случаях - Значение: Когда произошли настоящие ошибки!

0
ответ дан 24 November 2019 в 10:23
поделиться

Законный случай для выдачи исключения:

  • Вы пытаетесь открыть файл, его нет, возникает исключение FileNotFoundException;

Незаконный случай:

  • Вы хотите что-то сделать только если файл не существует, вы пытаетесь открыть файл, а затем добавляете код в блок catch.

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

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

RecordIterator<MyObject> ri = createRecordIterator();
try {
   MyObject myobject = ri.next();
} catch(NoSuchElement exception) {
   // Object doesn't exist, will create it
}

Это было бы лучше:

RecordIterator<MyObject> ri = createRecordIterator();
if (ri.hasNext()) {
   // It exists! 
   MyObject myobject = ri.next();
} else {
   // Object doesn't exist, will create it
}

КОММЕНТАРИЙ, ДОБАВЛЕННЫЙ К ОТВЕТУ:

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

Комментарии по этому поводу могут добавить больше, чем сам мой ответ.

1
ответ дан 24 November 2019 в 10:23
поделиться

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

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

0
ответ дан 24 November 2019 в 10:23
поделиться

Одна очень практическая причина состоит в том, что при отладке программы я часто переключаю на «Исключения первого шанса» (Отладка -> Исключения) для отладки приложения. Если происходит много исключений, очень сложно найти, где что-то пошло «не так».

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

2
ответ дан 24 November 2019 в 10:23
поделиться
  1. Ремонтопригодность: как упоминалось люди выше, бросание исключений в мгновение ока сродни использованию gotos.
  2. Взаимодействие: вы не можете связать библиотеки C ++ с модулями C / Python (по крайней мере, не легко), если вы используете исключения.
  3. Ухудшение производительности : RTTI используется для фактического поиска типа исключения, которое накладывает дополнительные накладные расходы. Таким образом, исключения не подходят для обработки часто встречающихся случаев использования (пользователь вводит int вместо строки и т. Д.).
2
ответ дан 24 November 2019 в 10:23
поделиться

Это плохой пример использования исключений в качестве поток управления:

int getTotalIncome(int incomeType) {
   int totalIncome= 0;
   try {
      totalIncome= calculateIncomeAsTypeA();
   } catch (IncorrectIncomeTypeException& e) {
      totalIncome= calculateIncomeAsTypeB();
   }

   return totalIncome;
}

Что очень плохо, но вы должны написать:

int getTotalIncome(int incomeType) {
   int totalIncome= 0;
   if (incomeType == A) {
      totalIncome= calculateIncomeAsTypeA();
   } else if (incomeType == B) {
      totalIncome= calculateIncomeAsTypeB();
   }
   return totalIncome;
}

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

Исключения также связаны с некоторыми потерями производительности, но проблемы с производительностью должны соответствовать правилу: «преждевременная оптимизация - корень всех зол»

2
ответ дан 24 November 2019 в 10:23
поделиться

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

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

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

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

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

3
ответ дан 24 November 2019 в 10:23
поделиться

Я упоминал об этой проблеме в статье об исключениях C ++ .

Соответствующая часть:

Почти всегда использование исключений для воздействия на «нормальный» поток плохая идея. Как мы уже обсуждали в разделе 3.1, исключения генерируют невидимые пути кода. Эти пути кода, вероятно, приемлемы, если они выполняются только в сценариях обработки ошибок. Однако, если мы используем исключения для каких-либо других целей, наше "нормальное" выполнение кода делится на видимую и невидимую части, что затрудняет чтение, понимание и расширение кода.

5
ответ дан 24 November 2019 в 10:23
поделиться

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

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

6
ответ дан 24 November 2019 в 10:23
поделиться

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

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

Существует множество мнений и компромиссов. Найдите то, что с вами говорит, и следуйте за ним.

6
ответ дан 24 November 2019 в 10:23
поделиться

На самом деле консенсуса нет. Вся проблема в некоторой степени субъективна, потому что «уместность» выброса исключения часто подсказывается существующими практиками в стандартной библиотеке самого языка. Стандартная библиотека C ++ выдает исключения намного реже, чем, скажем, стандартная библиотека Java, которая почти всегда предпочитает исключения, даже для ожидаемых ошибок, таких как недопустимый ввод пользователя (например, Scanner.nextInt ). Я считаю, что это существенно влияет на мнение разработчиков о том, когда уместно генерировать исключение.

Как программист на C ++, я лично предпочитаю резервировать исключения для очень «исключительных» обстоятельств, например, нехватка памяти, нехватка места на диске. , апокалипсис и т. д. Но я не Я настаиваю на том, что это абсолютно правильный способ делать вещи.

11
ответ дан 24 November 2019 в 10:23
поделиться

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

Причина в том, что без исключений вызов функции может либо return, или он может сначала завершить программу. За исключениями, вызов функции может либо вернуть, либо завершить программу, либо перейти к блоку catch где-нибудь. Таким образом, вы больше не можете следить за потоком управления, просто глядя на код перед собой. Вам нужно знать, могут ли вызываемые функции выбрасывать. Возможно, вам потребуется знать, что может быть брошено и где оно поймано, в зависимости от того, заботитесь ли вы, куда идет контроль, или заботитесь только о том, чтобы он покидал текущую область.

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

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

16
ответ дан 24 November 2019 в 10:23
поделиться

Я бы сказал, что исключения - это механизм, позволяющий безопасно вывести вас из текущего контекста (из текущего кадра стека в простейшем смысле, но не только). Структурированное программирование ближе всего к goto. Чтобы использовать исключения так, как они были задуманы, у вас должна быть ситуация, когда вы не можете продолжать то, что делаете сейчас, и не можете справиться с этим в той точке, где вы находитесь сейчас. Так, например, если пароль пользователя неверен, вы можете продолжить, вернув false. Но если подсистема пользовательского интерфейса сообщает, что не может даже запросить пользователя, просто возвращать «не удалось войти в систему» ​​было бы неправильно. Текущий уровень кода просто не знает, что делать. Таким образом, он использует механизм исключения, чтобы делегировать ответственность кому-то выше, кто может знать, что делать.

2
ответ дан 24 November 2019 в 10:23
поделиться

Выдача исключения в некоторой степени аналогична оператору goto . Сделайте это для управления потоком, и вы получите непонятный спагетти-код. Хуже того, в некоторых случаях вы даже не знаете, куда именно переходит переход (т. Е. Если вы не улавливаете исключение в данном контексте). Это явно нарушает принцип «наименьшего удивления», который повышает ремонтопригодность.

22
ответ дан 24 November 2019 в 10:23
поделиться

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

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

Конструкторы

Вы очень мало можете сказать о каждом конструкторе для каждого класса, который мог бы быть написан на C ++, но есть несколько вещей. Главный из них заключается в том, что построенные объекты (т. Е. Для которых конструктор завершился возвратом) будут уничтожены. Вы не можете изменить это постусловие, потому что язык предполагает, что оно истинно, и автоматически вызывает деструкторы. (Технически вы можете согласиться с возможностью неопределенного поведения, для которого язык не дает гарантий чего-либо, , но это, вероятно, лучше описать в другом месте.)

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

// boost::lexical_cast<int>() is the parsing function here
void show_square() {
  using namespace std;
  assert(cin); // precondition for show_square()
  cout << "Enter a number: ";
  string line;
  if (!getline(cin, line)) { // EOF on cin
    // error handling omitted, that EOF will not be reached is considered
    // part of the precondition for this function for the sake of example
    //
    // note: the below Python version throws an EOFError from raw_input
    //  in this case, and handling this situation is the only difference
    //  between the two
  }
  int n;
  try {
    n = boost::lexical_cast<int>(line);
    // lexical_cast returns an int
    // if line == "abc", it obviously cannot meet that postcondition
  }
  catch (boost::bad_lexical_cast&) {
    cout << "I can't do that, Dave.\n";
    return;
  }
  cout << n * n << '\n';
}

К сожалению, здесь показаны два примера того, как область видимости C ++ требует от вас нарушения RAII / SBRM. Пример на Python, который не имеет этой проблемы и показывает то, что я хотел бы иметь в C ++ - try-else:

# int() is the parsing "function" here
def show_square():
  line = raw_input("Enter a number: ") # same precondition as above
  # however, here raw_input will throw an exception instead of us
  # using assert
  try:
    n = int(line)
  except ValueError:
    print "I can't do that, Dave."
  else:
    print n * n

Предварительные условия

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

В частности, сравните ветки std :: logic_error и std :: runtime_error библиотеки stdlib. иерархия исключений. Первый часто используется для нарушений предусловия, тогда как последний больше подходит для нарушений постусловия.

61
ответ дан 24 November 2019 в 10:23
поделиться

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

Я только что читал эту статью MSDN об улучшении производительности управляемого кода , и эта часть напомнила мне об этом вопросе:

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

Конечно, это только для .NET, и оно также предназначено специально для тех, кто разрабатывает высокопроизводительные приложения (например, я); так что это, очевидно, не универсальная правда. Тем не менее, среди нас много разработчиков .NET, поэтому я подумал, что это стоит отметить.

EDIT :

Хорошо, прежде всего, давайте проясним одну вещь: Я не собираюсь драться с кем-либо по поводу производительности. В общем, по сути, я склонен согласиться с теми, кто считает преждевременную оптимизацию грехом. Однако позвольте мне отметить два момента:

  1. Плакат требует объективного обоснования общепринятого мнения о том, что исключения следует использовать с осторожностью. Мы можем обсудить удобочитаемость и правильный дизайн сколько угодно; но это субъективные вопросы, когда люди готовы спорить с обеих сторон. Я думаю, что автор этого знает. Дело в том, что использование исключений для управления потоком программы часто является неэффективным способом решения задач. Нет, не всегда , но часто . Вот почему разумный совет - экономно использовать исключения, так же как и умеренно есть красное мясо или пить вино.

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

Чтобы проиллюстрировать мою точку зрения, рассмотрим следующие примеры кода C #.

Пример 1: Обнаружение недопустимого ввода пользователя

Это пример того, что я бы назвал исключением злоупотреблением .

int value = -1;
string input = GetInput();
bool inputChecksOut = false;

while (!inputChecksOut) {
    try {
        value = int.Parse(input);
        inputChecksOut = true;

    } catch (FormatException) {
        input = GetInput();
    }
}

Этот код мне кажется нелепым. Конечно работает . С этим никто не спорит. Но это должно быть примерно таким:

int value = -1;
string input = GetInput();

while (!int.TryParse(input, out value)) {
    input = GetInput();
}

Пример 2: Проверка существования файла

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

string text = null;
string path = GetInput();
bool inputChecksOut = false;

while (!inputChecksOut) {
    try {
        using (FileStream fs = new FileStream(path, FileMode.Open)) {
            using (StreamReader sr = new StreamReader(fs)) {
                text = sr.ReadToEnd();
            }
        }

        inputChecksOut = true;

    } catch (FileNotFoundException) {
        path = GetInput();
    }
}

Это кажется достаточно разумным, не так ли? Мы пытаемся открыть файл; если его там нет, мы перехватываем это исключение и пытаемся открыть другой файл ... Что в этом плохого?

Ничего особенного. Но рассмотрите эту альтернативу, которая не не вызывает никаких исключений:

string text = null;
string path = GetInput();

while (!File.Exists(path)) path = GetInput();

using (FileStream fs = new FileStream(path, FileMode.Open)) {
    using (StreamReader sr = new StreamReader(fs)) {
        text = sr.ReadToEnd();
    }
}

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

Использование try / catch блок: 25,455 секунд
Использование int.TryParse ]: 1.637 миллисекунд

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

Использование try / catch блок: 29,989 секунд
Использование File.Exists : 22,820 миллисекунд

Многие люди ответили бы на это, сказав: «Да, выбросить и отловить 10 000 исключений - это крайне нереально; это преувеличивает результаты». Конечно, есть. Разница между выдачей одного исключения и обработкой некорректного ввода самостоятельно не будет заметна пользователю. Факт остается фактом: использование исключений в этих двух случаях от 1000 до более чем 10 000 раз медленнее , чем альтернативные подходы, которые столь же удобочитаемы, если не больше.

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

Опять же, это всего лишь два примера. Конечно, будут моменты, когда снижение производительности при использовании исключений не будет таким серьезным (Павел прав; в конце концов, это зависит от реализации). Все я Я говорю: давайте посмотрим правде в глаза, ребята - в случаях, подобных описанному выше, выдача и перехват исключения аналогичны GetNine () ; это просто неэффективный способ сделать что-то , которое легко можно было бы сделать лучше .


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

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

Вы также можете спросить: почему этот код плохой?

private int GetNine() {
    for (int i = 0; i < 10; i++) {
        if (i == 9) return i;
    }
}

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

Это то, что люди имеют в виду, когда говорят об исключительном «злоупотреблении».

7
ответ дан 24 November 2019 в 10:23
поделиться

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

-1
ответ дан 24 November 2019 в 10:23
поделиться

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

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

Политика кодирования Google предписывает никогда не использовать исключения , особенно в C ++. Ваше приложение либо не готово к обработке исключений, либо готово. Если это не так, то исключение, вероятно, будет распространяться до тех пор, пока ваше приложение не умрет.

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

2
ответ дан 24 November 2019 в 10:23
поделиться

Мой подход к обработке ошибок состоит в том, что существует три основных типа ошибок:

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

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

5
ответ дан 24 November 2019 в 10:23
поделиться

Я не думаю, что исключения следует использовать редко. Но.

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

Если вы собираетесь широко использовать исключения, то:

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

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

  • вы не пропустите и не проигнорируете ошибки
  • вы убежище t написать, что проверки кодов возврата, фактически не зная, что делать с неправильным кодом на низком уровне
  • , когда вы вынуждены писать безопасный в отношении исключений код, он становится более структурированным
7
ответ дан 24 November 2019 в 10:23
поделиться
  1. Дорогие
    вызовы ядра (или другие вызовы системного API) для управления сигнальными интерфейсами ядра (системы)
  2. Трудно анализировать
    Многие проблемы goto заявление применяется к исключениям. Они часто перепрыгивают через потенциально большие объемы кода в нескольких подпрограммах и исходных файлах. Это не всегда очевидно при чтении промежуточного исходного кода. (Это на Java.)
  3. Не всегда ожидается промежуточным кодом
    Код, который перескакивает, мог быть или не быть написан с учетом возможности выхода исключения. Если изначально так было написано, возможно, оно не было сохранено с учетом этого. Подумайте: утечки памяти, утечки файловых дескрипторов, утечки сокетов, кто знает?
  4. Сложности обслуживания
    Труднее поддерживать код, который перескакивает через исключения обработки.
40
ответ дан 24 November 2019 в 10:23
поделиться

В C ++ есть несколько причин.

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

Во-вторых, C ++ не имеет встроенной сборки мусора, поэтому классы C ++, которые владеют ресурсами, избавляются из них в их деструкторах. Следовательно, при обработке исключений C ++ система должна запускать все деструкторы в области видимости. В языках с GC и без настоящих конструкторов, таких как Java, генерирование исключений намного менее обременительно.

В-третьих, сообщество C ++, включая Бьярна Страуструпа, Комитет по стандартам и различных разработчиков компиляторов, полагало, что исключения должны быть исключительными. В общем, против языковой культуры идти не стоит. Реализации основаны на предположении, что исключения будут редкими. В лучших книгах исключения рассматриваются как исключительные. В хорошем исходном коде есть несколько исключений. Хорошие разработчики C ++ рассматривают исключения как исключительные. Чтобы пойти против этого, вам нужна веская причина, и все причины, которые я вижу, направлены на то, чтобы сделать их исключительными.

не стоит идти против языковой культуры. Реализации основаны на предположении, что исключения будут редкими. В лучших книгах исключения рассматриваются как исключительные. В хорошем исходном коде есть несколько исключений. Хорошие разработчики C ++ рассматривают исключения как исключительные. Чтобы пойти против этого, вам нужна веская причина, и все причины, которые я вижу, направлены на то, чтобы сделать их исключительными.

не стоит идти против языковой культуры. Реализации основаны на предположении, что исключения будут редкими. В лучших книгах исключения рассматриваются как исключительные. В хорошем исходном коде есть несколько исключений. Хорошие разработчики C ++ рассматривают исключения как исключительные. Чтобы пойти против этого, вам нужна веская причина, и все причины, которые я вижу, направлены на то, чтобы сделать их исключительными.

3
ответ дан 24 November 2019 в 10:23
поделиться

Мои два цента:

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

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

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

Для ParseInt, как упоминалось ранее, мне нравится идея исключений. Просто верните значение. Если параметр не поддается синтаксическому анализу, вызовите исключение. С одной стороны, это делает ваш код чище. На уровне вызова вам нужно сделать что-то вроде

try 
{
   b = ParseInt(some_read_string);
} 
catch (ParseIntException &e)
{
   // use some default value instead
   b = 0;
}

Код чистый. Когда я получаю ParseInt, как этот, разбросанный повсюду, я создаю функции-оболочки, которые обрабатывают исключения и возвращают мне значения по умолчанию. Например,

int ParseIntWithDefault(String stringToConvert, int default_value=0)
{
   int result = default_value;
   try
   {
     result = ParseInt(stringToConvert);
   }
   catch (ParseIntException &e) {}

   return result;
}

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

  • исключения еще нужно где-то обрабатывать. Дополнительная проблема: c ++ не имеет синтаксиса, который позволяет ему указывать, какие исключения может вызывать функция (как это делает java). Таким образом, вызывающий уровень не знает, какие исключения могут потребоваться обработать.
  • иногда код может быть очень подробным, если каждая функция должна быть заключена в блок try / catch. Но иногда это все же имеет смысл.

Так что иногда бывает трудно найти хороший баланс.

0
ответ дан 24 November 2019 в 10:23
поделиться

Кто сказал, что их следует использовать консервативно? Просто никогда не используйте исключения для управления потоком, и все. И кого волнует стоимость исключения, когда оно уже создано?

0
ответ дан 24 November 2019 в 10:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: