Язык Google Go не имеет исключений как выбор дизайна, а Линус, известный в Linux, назвал исключения чушью. Почему?
Почему плохая обработка исключений?
Ответы (15)
Исключения упрощают написание кода, в котором генерируемое исключение нарушает инварианты и оставляет объекты в несогласованном состоянии. По сути, они заставляют вас помнить, что почти каждое ваше утверждение потенциально может быть выброшено, и обрабатывать это правильно. Это может быть сложно и нелогично.
Рассмотрим на простом примере что-то вроде этого:
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 ++ написаны целые книги. Многие эксперты ошиблись. Если это действительно так сложно и имеет так много нюансов, возможно, это хороший знак, что вам нужно игнорировать эту функцию. :-)
Причина отсутствия исключений в Go объясняется в FAQ по языковому дизайну Go:
Исключения - похожая история. Было предложено несколько вариантов исключений, но каждый из них значительно усложняет язык и время выполнения. По самой своей природе исключения охватывают функции и, возможно, даже горутины; они имеют самые разные последствия. Также есть опасения по поводу того, какое влияние они окажут на библиотеки. Они, по определению, являются исключительными, но опыт работы с другими поддерживающими их языками показывает, что они оказывают глубокое влияние на спецификации библиотек и интерфейсов. Было бы неплохо найти дизайн, который позволил бы им быть действительно исключительными, не поощряя превращение распространенных ошибок в особый поток управления, который требует компенсации от каждого программиста.
Как и дженерики, исключения остаются открытой проблемой.
Другими словами, они еще не выяснили, как поддерживать исключения в Go таким образом, который, по их мнению, является удовлетворительным. Они не говорят, что исключения плохи сами по себе;
ОБНОВЛЕНИЕ - май 2012 г.
Дизайнеры Go слезли с заборов. В их FAQ теперь говорится следующее:
Мы считаем, что привязка исключений к структуре управления, как в идиоме try-catch-finally, приводит к запутанному коду. Это также побуждает программистов отмечать слишком много обычных ошибок, таких как невозможность открытия файла, как исключительные.
Go использует другой подход. Для простой обработки ошибок многозначные возвращаемые значения Go позволяют легко сообщить об ошибке, не перегружая возвращаемое значение. Канонический тип ошибки в сочетании с другими функциями Go делает обработку ошибок приятной, но сильно отличается от таковой на других языках.
Go также имеет несколько встроенных функций для сигнализации и восстановления после действительно исключительных условий. Механизм восстановления выполняется только как часть состояния функции, сбрасываемой после ошибки, что достаточно для обработки катастрофы, но не требует дополнительных структур управления и при правильном использовании может привести к созданию чистого кода обработки ошибок.
Подробнее см. В статье «Отложить, паника и восстановление».
Итак, краткий ответ заключается в том, что они могут делать это по-другому, используя многозначный возврат. (И у них в любом случае есть форма обработки исключений.)
... и Линус, известный в Linux, назвал исключения чушью.
Если вы хотите знать, почему Линус считает исключения - это чушь, лучше всего поискать его работы по этой теме. Единственное, что я отследил до сих пор, - это цитата, встроенная в пару писем. на C ++:
Принципиально нарушена вся обработка исключений C ++. Особенно это плохо для ядер.
Вы заметите, что он говорит об исключениях C ++ в частности, а не об исключениях в целом. (И исключения C ++ действительно, по-видимому, имеют некоторые проблемы, из-за которых их сложно использовать правильно.)
Я пришел к выводу, что Линус вообще не называл исключения (в общем) чушью!
Исключения сами по себе неплохи, но если вы знаете, что они будут происходить часто, они могут быть дорогостоящими с точки зрения производительности.
Эмпирическое правило состоит в том, что исключения должны отмечать исключительные условия, и вы не должны использовать их для управления ходом выполнения программы.
Я не согласен с тем, что «исключать исключения можно только в исключительной ситуации». Хотя в целом это правда, это заблуждение. Исключения составляют условия ошибки (сбои выполнения).
Независимо от того, какой язык вы используете, возьмите копию Руководства по разработке платформы: условные обозначения, идиомы и шаблоны для многоразовых библиотек .NET (2-е издание). Глава о выбросе исключений не имеет аналогов. Несколько цитат из первого издания (2-е у меня на работе):
- НЕ возвращать коды ошибок.
- Коды ошибок можно легко проигнорировать, что часто бывает.
- Исключения являются основным средством сообщения об ошибках в рамках.
- Хорошее практическое правило состоит в том, что если метод не выполняет то, что предполагает его название, это следует рассматривать как сбой на уровне метода, приводящий к исключению.
- НЕ используйте исключения для нормального потока управления, если это возможно.
Есть страницы с заметками о преимуществах исключений (согласованность API, выбор местоположения кода обработки ошибок, повышенная надежность и т. Д.). Есть раздел о производительности, который включает несколько шаблонов (Tester-Doer, Try-Parse).
Исключения и обработка исключений - это неплохо. Как и любую другую функцию, ими можно злоупотреблять.
С точки зрения golang, я полагаю, что отсутствие обработки исключений делает процесс компиляции простым и безопасным.
С точки зрения Линуса, я понимаю, что код ядра - это ВСЕ угловые случаи. Так что есть смысл отказаться от исключений.
Исключения имеют смысл в коде, если можно оставить текущую задачу на полу, и где общий код случая имеет большее значение, чем обработка ошибок. Но они требуют генерации кода от компилятора.
Например, они подходят для большинства высокоуровневых кодов, ориентированных на пользователя, таких как код веб-приложений и приложений для настольных компьютеров.
Исключения сами по себе не являются «плохими», это способ обработки исключений иногда бывает плохим. Есть несколько рекомендаций, которые можно применить при обработке исключений, чтобы облегчить некоторые из этих проблем. Некоторые из них включают (но, конечно, не ограничиваются):
- Не используйте исключения для управления потоком программы - т.е. не полагайтесь на операторы catch для изменения потока логики. Это не только скрывает различные детали логики, но и может привести к снижению производительности.
- Не генерируйте исключения из функции, если возвращаемый «статус» имеет больше смысла - генерируйте исключения только в исключительной ситуации. Создание исключений - дорогостоящая и требовательная к производительности операция. Например, если вы вызываете метод для открытия файла, но этот файл не существует, вызовите исключение FileNotFound. Если вы вызываете метод, который определяет, существует ли учетная запись клиента, возвращайте логическое значение, не возвращайте исключение «CustomerNotFound».
- При определении того, обрабатывать ли исключение или нет, не используйте предложение «try ... catch», если вы не можете сделать что-то полезное с исключением. Если вы не можете обработать исключение, просто позвольте ему всплыть в стеке вызовов. В противном случае исключения могут быть «проглочены» обработчиком, и детали будут потеряны (если вы не повторно выбросите исключение).
Option<T>
вместо null
. Например, только что познакомился с Java 8, взяв подсказку от Guava (и других).
- person Maarten Bodewes; 05.05.2014
Типичные аргументы заключаются в том, что невозможно определить, какие исключения будут исходить из определенного фрагмента кода (в зависимости от языка), и что они слишком похожи на goto
s, что затрудняет мысленное отслеживание выполнения.
http://www.joelonsoftware.com/items/2003/10/13.html
По этому поводу однозначно нет единого мнения. Я бы сказал, что с точки зрения жесткого программиста на C, такого как Линус, исключения - определенно плохая идея. Однако типичный программист на Java находится в совершенно другой ситуации.
Исключения неплохие. Они хорошо вписываются в модель RAII C ++, которая является самой элегантной особенностью C ++. Если у вас уже есть код, небезопасный в отношении исключений, то он плох в этом контексте. Если вы пишете действительно низкоуровневое программное обеспечение, такое как ОС Linux, тогда оно плохое. Если вам нравится засорять свой код кучей проверок возврата ошибок, то они бесполезны. Если у вас нет плана управления ресурсами при возникновении исключения (которые предоставляют деструкторы C ++), то они плохие.
Таким образом, отличный вариант использования исключений ...
Допустим, вы работаете над проектом, и каждый контроллер (около 20 различных основных) расширяет один контроллер суперкласса с помощью метода действия. Затем каждый контроллер выполняет кучу вещей, отличных друг от друга, вызывая объекты B, C, D в одном случае и F, G, D в другом случае. Во многих случаях здесь на помощь приходят исключения, когда код возврата был огромным и КАЖДЫЙ контроллер обрабатывал его по-разному. Я взломал весь этот код, выбросил правильное исключение из «D», поймал его в методе действий контроллера суперкласса, и теперь все наши контроллеры согласованы. Раньше D возвращал null для НЕСКОЛЬКИХ различных случаев ошибок, о которых мы хотим сообщить конечному пользователю, но не могли, и я не хотел превращать StreamResponse в неприятный объект ErrorOrStreamResponse (смешивание структуры данных с ошибками, на мой взгляд, является неприятный запах, и я вижу, что много кода возвращают "Stream" или другой тип объекта со встроенной в него информацией об ошибке (это действительно должна быть функция, возвращающая структуру успеха ИЛИ структуру ошибки, которую я могу сделать с исключениями и кодами возврата ) .... хотя способ множественных ответов C # - это то, что я могу иногда рассмотреть, хотя во многих случаях исключение может пропускать множество слоев (слоев, которые мне не нужны для очистки ресурсов).
да, мы должны беспокоиться о каждом уровне и любой очистке / утечке ресурсов, но в целом ни у одного из наших контроллеров не было ресурсов для очистки после.
слава богу, у нас были исключения, иначе мне пришлось бы провести огромный рефакторинг и потратить слишком много времени на то, что должно быть простой проблемой программирования.
Теоретически они действительно плохие. В идеальном математическом мире исключительных ситуаций быть не может. Посмотрите на функциональные языки, у них нет побочных эффектов, поэтому у них практически нет источника для обычных ситуаций.
Но реальность - это совсем другое дело. У нас всегда бывают «неожиданные» ситуации. Вот почему нам нужны исключения.
Я думаю, что мы можем рассматривать исключения как синтаксический сахар для ExceptionSituationObserver. Вы просто получаете уведомления об исключениях. Больше ничего.
Я думаю, что в Go они представят кое-что, что поможет справиться с «неожиданными» ситуациями. Я могу предположить, что они попытаются сделать так, чтобы это звучало менее деструктивно, как исключения, и больше как логика приложения. Но это только мое предположение.
Парадигма обработки исключений C ++, которая составляет частичную основу для Java и, в свою очередь, .net, вводит несколько хороших концепций, но также имеет некоторые серьезные ограничения. Одно из ключевых намерений разработки обработки исключений - позволить методам гарантировать, что они будут либо удовлетворять своим постусловиям, либо генерировать исключение, а также гарантировать, что любая очистка, которая должна произойти перед выходом метода, произойдет. К сожалению, парадигмы обработки исключений в C ++, Java и .net не могут предоставить никаких хороших средств обработки ситуации, когда неожиданные факторы препятствуют выполнению ожидаемой очистки. Это, в свою очередь, означает, что нужно либо рискнуть, что все остановится, если произойдет что-то неожиданное (подход C ++ к обработке исключения происходит во время раскрутки стека), либо принять возможность того, что условие, которое не может быть разрешено из-за возникшей проблемы во время очистки стека будет ошибочно принята за проблему, которая может быть разрешена (и могла бы быть, если бы очистка была успешной), или принять возможность того, что неразрешимая проблема, очистка которой с разматыванием стека вызывает исключение, которое обычно разрешается, может пойти незамеченным, поскольку код, который обрабатывает последнюю проблему, объявляет ее «решенной».
Даже если обработка исключений в целом будет хорошей, вполне разумно рассматривать как неприемлемую парадигму обработки исключений, которая не может обеспечить хорошие средства для решения проблем, возникающих при очистке после других проблем. Это не означает, что фреймворк не может быть спроектирован с парадигмой обработки исключений, которая могла бы гарантировать разумное поведение даже в сценариях с множественными отказами, но ни один из ведущих языков или фреймворков пока не может этого сделать.
Я не читал все другие ответы, поэтому об этом уже упоминалось, но есть одна критика в том, что они вызывают разрыв программ в длинных цепочках, что затрудняет отслеживание ошибок при отладке кода. Например, если Foo () вызывает Bar (), который вызывает Wah (), который вызывает ToString (), то случайное нажатие неправильных данных в ToString () в конечном итоге выглядит как ошибка в Foo (), почти полностью не связанной функции.
Для меня вопрос очень простой. Многие программисты неправильно используют обработчик исключений. Больше языковых ресурсов - лучше. Уметь обрабатывать исключения - это хорошо. Одним из примеров неправильного использования является значение, которое должно быть целым числом, которое не проверяется, или другой ввод, который может делиться и не проверяться на деление нуля ... обработка исключений может быть простым способом избежать дополнительной работы и тяжелого мышления, программист может захотеть сделать грязный ярлык и применить обработку исключений ... Утверждение: «профессиональный код НИКОГДА не дает сбоев» может быть иллюзорным, если некоторые из проблем, обрабатываемых алгоритмом, являются неопределенными по своей природе. Возможно, в неизвестных по своей природе ситуациях хорошо сработает обработчик исключений. Хорошие методы программирования являются предметом дискуссий.
- Необработанное исключение - это вообще плохо.
- Плохо обработанное исключение - это плохо (конечно).
- «Хорошее / плохое» обработки исключений зависит от контекста / области видимости и уместности, а не ради этого.
Хорошо, здесь скучный ответ. Думаю, это действительно зависит от языка. Если исключение может привести к тому, что выделенные ресурсы останутся позади, их следует избегать. В языках сценариев они просто покидают или перепрыгивают через части потока приложения. Само по себе это неприятно, но избежать почти фатальных ошибок с помощью исключений - приемлемая идея.
Для сообщений об ошибках я обычно предпочитаю сигналы об ошибках. Все зависит от API, варианта использования и серьезности, а также от того, достаточно ли ведения журнала. Также я пытаюсь переопределить поведение и вместо этого throw Phonebooks()
. Идея состоит в том, что «Исключения» часто заходят в тупик, но «Телефонная книга» содержит полезную информацию о восстановлении после ошибок или альтернативных маршрутах выполнения. (Пока не нашел подходящего варианта использования, но продолжайте попытки.)