Почему исключения должны использоваться консервативно? - PullRequest
77 голосов
/ 16 ноября 2009

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

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

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

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

Ответы [ 28 ]

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

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

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

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

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

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

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

5 голосов
/ 16 ноября 2009

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

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

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

5 голосов
/ 16 ноября 2009

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

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

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

3 голосов
/ 17 ноября 2009

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

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

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

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

3 голосов
/ 16 ноября 2009

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

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

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

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

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

2 голосов
/ 17 ноября 2009

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

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

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

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

2 голосов
/ 16 ноября 2009

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

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 голосов
/ 16 ноября 2009

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

2 голосов
/ 17 ноября 2009

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

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

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