Почему обработка исключений плохая? - PullRequest
87 голосов
/ 15 ноября 2009

Язык Google Go не имеет исключений в качестве выбора дизайна, а Линус из известности Linux назвал исключения дерьмом. Почему?

Ответы [ 15 ]

75 голосов
/ 15 ноября 2009

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

Рассмотрим что-то вроде этого в качестве простого примера:

class Frobber
{
    int m_NumberOfFrobs;
    FrobManager m_FrobManager;

public:
    void Frob()
    {
        m_NumberOfFrobs++;

        m_FrobManager.HandleFrob(new FrobObject());
    }
};

Предполагая, что FrobManager будет delete FrobObject, это выглядит хорошо, верно? Или, может быть, нет ... Представьте себе, если либо FrobManager::HandleFrob(), либо operator new выдает исключение. В этом примере приращение m_NumberOfFrobs не откатывается. Таким образом, любой, кто использует этот экземпляр Frobber, будет иметь возможно поврежденный объект.

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

Например, вы можете думать об этом так же, как о мьютексах. Внутри критического раздела вы полагаетесь на несколько операторов, чтобы убедиться, что структуры данных не повреждены и что другие потоки не могут видеть ваши промежуточные значения. Если какое-либо из этих утверждений просто случайно не выполняется, вы попадаете в мир боли. Теперь уберите блокировки и параллелизм, и подумайте о каждом из этих методов. Думайте о каждом методе как о транзакции перестановок в состоянии объекта, если хотите. В начале вашего вызова метода объект должен быть в чистом состоянии, а в конце также должно быть чистое состояние. Между ними переменная foo может не соответствовать bar, но ваш код в конечном итоге исправит это. Что означает исключение, так это то, что любое из ваших утверждений может прервать вас в любое время. В каждом отдельном методе лежит ответственность за вас, чтобы все было правильно и откатывался, когда это происходит, или упорядочивал ваши операции так, чтобы броски не влияли на состояние объекта. Если вы ошиблись (а такую ​​ошибку легко совершить), то вызывающая сторона в итоге увидит ваши промежуточные значения.

Такие методы, как RAII, которые программисты C ++ любят упоминать в качестве окончательного решения этой проблемы, имеют большое значение для защиты от этого. Но они не серебряная пуля. Это гарантирует, что вы освободите ресурсы при броске, но не освобождает вас от необходимости думать о повреждении состояния объекта и вызывающих, видящих промежуточные значения. Таким образом, для многих людей проще сказать по указанию стиля кодирования: без исключений . Если вы ограничиваете тип кода, который пишете, эти ошибки сложнее представить. Если вы этого не сделаете, то легко ошибиться.

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

49 голосов
/ 15 ноября 2009

Причина, по которой Go не имеет исключений, объясняется в разделе часто задаваемых вопросов по языку Go:

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

Как и дженерики, исключения остаются открытый вопрос.

Другими словами, они еще не выяснили, как поддерживать исключения в Go таким способом, который они считают удовлетворительным. Они не говорят, что исключения являются плохими per se ;

ОБНОВЛЕНИЕ - май 2012

Дизайнеры Go теперь спустились с забора. Их часто задаваемые вопросы теперь говорят это:

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

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

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

Подробнее см. В статье «Отсрочка, паника и восстановление».

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


... и Линус из Linux славы назвал исключения дерьмом.

Если вы хотите знать, почему Линус считает, что исключения - это дерьмо, лучше всего поискать его записи на эту тему. Единственное, что я до сих пор отследил, - это цитата, встроенная в пару писем на C ++ :

"Все, что связано с обработкой исключений в C ++, в корне сломано. Особенно это касается ядер."

Вы заметите, что он говорит об исключениях C ++ в частности, а не об исключениях вообще. (И исключения C ++ do , очевидно, имеют некоторые проблемы, которые затрудняют их правильное использование.)

Я пришел к выводу, что Линус вообще не называл исключения (вообще) "дерьмом"!

29 голосов
/ 15 ноября 2009

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

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

24 голосов
/ 15 ноября 2009

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

Независимо от того, какой язык вы используете, возьмите копию Руководства по проектированию платформы : условные обозначения, идиомы и шаблоны для многократно используемых библиотек .NET (2-е издание). Глава по выбрасыванию исключений не имеет аналогов. Некоторые цитаты из первого издания (2-го на моей работе):

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

Существуют страницы заметок о преимуществах исключений (непротиворечивость API, выбор местоположения кода обработки ошибок, повышенная надежность и т. Д.). Есть раздел о производительности, который включает несколько шаблонов (Tester-Doer, Try-Parse).

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

11 голосов
/ 15 ноября 2009

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

  1. Не используйте исключения для управления потоком программы - т.е. не полагайтесь на операторы «catch» для изменения потока логики. Это не только скрывает различные детали вокруг логики, но и может привести к снижению производительности.
  2. Не выбрасывать исключения из функции, когда возвращаемый «статус» будет иметь больше смысла - только выбрасывать исключения в исключительной ситуации. Создание исключений - дорогостоящая, требующая высокой производительности операция. Например, если вы вызываете метод для открытия файла, и этот файл не существует, выдается исключение «FileNotFound». Если вы вызываете метод, который определяет, существует ли учетная запись клиента, верните логическое значение, не возвращайте исключение «CustomerNotFound».
  3. При определении, обрабатывать или нет исключение, не используйте предложение try ... catch, если только вы не можете сделать что-то полезное с исключением. Если вы не можете обработать исключение, вы должны просто позволить ему пузыриться в стеке вызовов. В противном случае обработчик может «проглотить» исключения, и детали будут потеряны (если вы не сбросите исключение).
11 голосов
/ 15 ноября 2009

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

С точки зрения Линуса, я понимаю, что код ядра - это ВСЕ о угловых случаях. Поэтому имеет смысл отказаться от исключений.

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

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

9 голосов
/ 15 ноября 2009

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

http://www.joelonsoftware.com/items/2003/10/13.html

Определенно нет единого мнения по этому вопросу. Я бы сказал, что с точки зрения такого программиста, как Линус, исключения - определенно плохая идея. Типичный Java-программист находится в совершенно другой ситуации.

7 голосов
/ 15 ноября 2009

Исключения не плохие. Они хорошо вписываются в модель RAII C ++, которая является самой элегантной вещью в C ++. Если у вас уже есть куча кода, который не является безопасным для исключений, то он плох в этом контексте. Если вы пишете действительно низкоуровневое программное обеспечение, такое как ОС Linux, то это плохо. Если вам нравится засорять ваш код кучей проверок на возврат ошибок, то они бесполезны. Если у вас нет плана управления ресурсами при возникновении исключения (которое предоставляет деструктор C ++), тогда они плохие.

4 голосов
/ 03 июля 2014

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

Допустим, вы работаете над проектом, и каждый контроллер (около 20 различных основных) расширяет один контроллер суперкласса методом действия. Затем каждый контроллер выполняет кучу вещей, отличающихся друг от друга, вызывая объекты B, C, D в одном случае и F, G, D в другом случае. Исключения приходят на помощь здесь во многих случаях, когда было много кода возврата, и КАЖДЫЙ контроллер работал с ним по-разному. Я разбил весь этот код, выдал правильное исключение из «D», поймал его в методе действия контроллера суперкласса, и теперь все наши контроллеры согласованы. Ранее D возвращал NULL для НЕСКОЛЬКО различных случаев ошибок, о которых мы хотим сообщить конечному пользователю, но не смог, и я не хотел превращать StreamResponse в неприятный объект ErrorOrStreamResponse (на мой взгляд, смешивать структуру данных с ошибками неприятный запах, и я вижу много кода, возвращающего «поток» или объект другого типа со встроенной в него информацией об ошибке (это должна быть функция, возвращающая структуру успеха ИЛИ структуру ошибки, которую я могу сделать с исключениями или кодами возврата) ) .... хотя способ множественных ответов в C # - это то, что я мог бы иногда рассмотреть, хотя во многих случаях исключение может пропускать большое количество слоев (слоев, которые мне не нужны для очистки ресурсов).

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

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

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

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

Но реальность - это другая история. У нас всегда бывают ситуации, которые являются «неожиданными». Вот почему нам нужны исключения.

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

С Go я думаю, что они представят что-то, что будет иметь дело с "неожиданными" ситуациями Я могу предположить, что они попытаются сделать так, чтобы это звучало менее разрушительно как исключения и больше как логика приложения Но это только мое предположение.

...