Обработка исключений - Википедия - Exception handling

В вычисление и компьютерное программирование, Обработка исключений это процесс реагирования на возникновение исключения - аномальные или исключительные условия, требующие специальной обработки - во время исполнение из программа. Как правило, исключение прерывает нормальный поток выполнения и выполняет предварительно зарегистрированный обработчик исключений; детали того, как это делается, зависят от того, аппаратное обеспечение или же программного обеспечения исключение и как реализовано программное исключение. Предоставляется специализированными язык программирования конструкции, аппаратные механизмы типа прерывает, или же Операционная система (ОПЕРАЦИОННЫЕ СИСТЕМЫ) межпроцессного взаимодействия (IPC) объекты, такие как сигналы. Некоторые исключения, особенно аппаратные, могут обрабатываться настолько изящно, что выполнение может возобновиться с того места, где оно было прервано.

Альтернативный подход к обработке исключений в программном обеспечении: проверка ошибок, который поддерживает нормальный поток программы с более поздними явными проверками на случай непредвиденных обстоятельств, сообщаемых с помощью специальных возвращаться ценности, вспомогательные глобальная переменная Такие как C's errno, или флаги состояния с плавающей запятой. Проверка ввода, который предварительно фильтрует исключительные случаи, также является подходом.

В аппаратном обеспечении

Механизмы аппаратных исключений обрабатываются ЦП. Он предназначен для поддержки, например, обнаружения ошибок и перенаправления потока программы на служебные процедуры обработки ошибок. Состояние до исключения сохраняется, например, в стеке.[1]

Аппаратная обработка исключений / ловушки: IEEE 754 с плавающей запятой

Обработка исключений в IEEE 754 плавающая точка стандарт оборудования относится в целом к ​​исключительным условиям и определяет исключение как «событие, которое происходит, когда операция с некоторыми конкретными операндами не имеет результата, подходящего для каждого разумного приложения. Эта операция может сигнализировать об одном или нескольких исключениях, вызывая значение по умолчанию или, если явно запрошена альтернативная обработка, определяемая языком ".

По умолчанию исключение IEEE 754 возобновляется и обрабатывается путем замены предопределенного значения для различных исключений, например бесконечность для деления на ноль исключения и обеспечение флаги состояния для последующей проверки того, произошло ли исключение (см. Язык программирования C99 для типичного примера обработки исключений IEEE 754). Стиль обработки исключений, обеспечиваемый использованием флагов состояния, включает: сначала вычисление выражения с использованием быстрой прямой реализации; проверка наличия сбоя путем тестирования флагов состояния; а затем, при необходимости, вызов более медленной, более устойчивой в числовом отношении реализации.[2]

Стандарт IEEE 754 использует термин «перехват» для обозначения вызова предоставляемой пользователем подпрограммы обработки исключений в исключительных условиях и является дополнительной функцией стандарта. Стандарт рекомендует несколько сценариев использования для этого, включая реализацию предварительной подстановки значения не по умолчанию с последующим возобновлением для краткой обработки устраняемые особенности.[2][3][4]

Поведение по умолчанию при обработке исключений IEEE 754 при возобновлении после предварительной подстановки значения по умолчанию позволяет избежать рисков, присущих изменению потока управления программой для числовых исключений. Например, в 1996 г. первый полет Ariane 5 (рейс 501) закончился катастрофическим взрывом отчасти из-за Ада политика обработки исключений языка программирования, заключающаяся в прерывании вычислений при арифметической ошибке, которая в данном случае была преобразованием 64-битного числа с плавающей запятой в 16-битное целое число переполнение.[3] В случае с Ariane Flight 501 программисты защитили только четыре из семи критических переменных от переполнения из-за проблем с вычислительными ограничениями бортового компьютера и полагались на то, что оказалось неверными предположениями о возможном диапазоне значений для три незащищенные переменные, потому что они повторно используемый код от Ariane 4, в отношении которого их предположения были верными.[5] В соответствии с Уильям Кахан, потери рейса 501 можно было бы избежать, если бы использовалась политика обработки исключений IEEE 754 с заменой по умолчанию, потому что переполнение 64-битного преобразования в 16-битное, которое привело к прерыванию программного обеспечения, произошло в фрагменте кода, который оказался быть совершенно ненужным на Ariane 5.[3] Официальный отчет о катастрофе (составлен комиссией по расследованию во главе с Жак-Луи Лайонс ) отметил, что «основной темой в разработке Ariane 5 является уклон в сторону уменьшение случайного отказа. Поставщик инерциальная навигационная система (SRI) только следовала данной спецификации, которая предусматривала, что в случае любого обнаруженного исключения процессор должен быть остановлен. Произошедшее исключение произошло не из-за случайного отказа, а из-за ошибки конструкции. Исключение было обнаружено, но было неправильно обработано, поскольку было принято мнение, что программное обеспечение следует считать правильным, пока не будет обнаружена его ошибка. [...] Хотя сбой произошел из-за систематической ошибки проектирования программного обеспечения, могут быть введены механизмы для смягчения этого типа проблемы. Например, компьютеры в SRI могли бы продолжать предоставлять свои наилучшие оценки требуемых отношение Информация. Есть основания опасаться, что программная исключительная ситуация должна быть разрешена или даже обязательна для остановки процессора при работе с критически важным оборудованием. Действительно, потеря правильной функции программного обеспечения опасна, потому что одно и то же программное обеспечение работает в обоих модулях SRI. В случае с Ariane 501 это привело к отключению двух все еще исправных критически важных единиц оборудования ».[6]

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

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

Unix-подобный операционные системы предоставлять средства для обработки исключений в программах через МПК. Как правило, прерывания, вызванные выполнением процесса, обрабатываются процедурами обслуживания прерываний операционной системы, и операционная система может затем отправить сигнал этому процессу, который мог попросить операционную систему зарегистрировать обработчик сигнала, который будет вызываться при возникновении сигнала, или позволить операционной системе выполнить действие по умолчанию (например, завершить программу). Типичные примеры: SIGSEGV, SIGBUS, СИГИЛЛ и SIGFPE.

Другие операционные системы, например, OS / 360 и последователи, может использовать другие подходы вместо или в дополнение к IPC.

В программном обеспечении

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

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

Языки программирования существенно различаются в понимании того, что такое исключение. Современные языки можно условно разделить на две группы:[7]

  • К этой категории относятся языки, на которых исключения предназначены для использования в качестве структур управления потоком: Ada, Java, Modula-3, ML, OCaml, PL / I, Python и Ruby.
  • Языки, в которых исключения используются только для обработки аномальных, непредсказуемых, ошибочных ситуаций: C ++,[8] C #, Common Lisp, Eiffel и Modula-2.

Кинири также отмечает, что «языковой дизайн лишь частично влияет на использование исключений и, следовательно, на способ обработки частичных и полных сбоев во время выполнения системы. Другое важное влияние - это примеры использования, обычно в основных библиотеках и примеры кода в технических книгах. , журнальные статьи и дискуссионные форумы в Интернете, а также стандарты кодекса организации ".[7]

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

История

Программная обработка исключений, разработанная в Лисп в 1960-1970-е гг. Это произошло в LISP 1.5 (1962), где были исключения пойманный посредством ERRSET ключевое слово, которое вернуло Ноль в случае ошибки, вместо завершения программы или входа в отладчик.[10] Ошибка повышение был представлен в MacLisp в конце 1960-х через ERR ключевое слово.[10] Это было быстро использовано не только для выявления ошибок, но и для нелокального потока управления, и поэтому было дополнено двумя новыми ключевыми словами: ЛОВИТЬ и БРОСАТЬ (MacLisp июнь 1972 г.), зарезервировав ERRSET и ERR для обработки ошибок. Поведение при очистке, которое теперь обычно называется "наконец", было введено в Ноль (Новая реализация LISP) в середине-конце 1970-х годов как ОТВЕТР-ЗАЩИТА.[11] Затем это было принято Common Lisp. Современник с этим был динамический ветер в Scheme, который обрабатывает исключения при закрытии. Первые статьи по структурированной обработке исключений были Гуденаф (1975a) и Гуденаф (1975b).[12] Впоследствии обработка исключений получила широкое распространение во многих языках программирования с 1980-х годов.

PL / I использовал исключения с динамической областью видимости, однако более поздние языки используют исключения с лексической областью видимости. Обработка исключений PL / I включала события, которые не являются ошибками, например, внимание, конец файла, изменение перечисленных переменных. Хотя некоторые более свежие языки поддерживают исключения, не связанные с ошибками, их использование не является обычным явлением.[нужна цитата ]

Первоначально обработка программных исключений включала как возобновляемые исключения (семантика возобновления), как и большинство аппаратных исключений, так и невозобновляемые исключения (семантика завершения). Однако семантика возобновления считалась неэффективной на практике в 1970-х и 1980-х годах (см. Обсуждение стандартизации C ++, цитируемое ниже)[13] и больше не используются, хотя и предоставляются такими языками программирования, как Common Lisp, Dylan и PL / I.

Семантика завершения

Механизмы обработки исключений в современных языках обычно не возобновляются («семантика завершения»), в отличие от аппаратных исключений, которые обычно возобновляются. Это основано на опыте использования обоих, поскольку есть теоретические и конструктивные аргументы в пользу любого решения; они широко обсуждались во время дискуссий о стандартизации C ++ в 1989–1991 годах, в результате чего было принято окончательное решение о семантике завершения.[13] Обосновании такой конструкции для механизма C ++, Страуструп Примечания:

[На] встрече в Пало-Альто [стандартизация C ++] в ноябре 1991 года мы услышали блестящее резюме аргументов в пользу семантики завершения, подкрепленное как личным опытом, так и данными из Джим Митчелл (от Sun, ранее от Xerox PARC). Джим использовал обработку исключений на полдюжине языков в течение 20 лет и был одним из первых сторонников возобновления семантики в качестве одного из основных разработчиков и разработчиков Xerox. Кедр / Меса система. Его сообщение было

«Прекращение предпочтительнее возобновления; это не вопрос мнения, а вопрос многолетнего опыта. Возобновление соблазнительно, но недействительно ».

Он подкрепил это заявление опытом работы с несколькими операционными системами. Ключевым примером был Cedar / Mesa: он был написан людьми, которые любили и использовали возобновление, но после десяти лет использования в системе в полмиллиона строк осталось только одно использование возобновления - и это был контекстный запрос. Поскольку на самом деле возобновление не было необходимо для такого запроса контекста, они удалили его и обнаружили значительное увеличение скорости в этой части системы. В каждом случае, когда использовалось возобновление, оно - за десять лет - становилось проблемой, и его заменял более подходящий дизайн. По сути, каждое использование возобновления означало неспособность сохранить отдельные уровни абстракции несвязанными.[12]

Критика

Противоположный взгляд на безопасность обработки исключений дал Тони Хоар в 1980 г., описывая Язык программирования Ада как имеющий "... множество функций и условных обозначений, многие из них ненужны, а некоторые из них, например, обработка исключений, даже опасны. [...] Не позволяйте этому языку в его нынешнем состоянии использоваться в приложениях, где надежность имеет решающее значение [...]. Следующая ракета, которая выйдет из строя в результате ошибки языка программирования, может не быть исследовательской космической ракетой в безвредном полете к Венере: это может быть ядерная боеголовка, взорвавшаяся над одним из наших городов . "[14]

Обработка исключений часто неправильно обрабатывается в программном обеспечении, особенно при наличии нескольких источников исключений; анализ потока данных из 5 миллионов строк кода Java обнаружено более 1300 дефектов обработки исключений.[15]Ссылаясь на многочисленные предыдущие исследования других авторов (1999–2004 гг.) И их собственные результаты, Веймер и Некула писали, что серьезная проблема с исключениями состоит в том, что они «создают скрытые пути потока управления, о которых программистам трудно рассуждать».[15]:8:27

Идти изначально был выпущен с явно опущенной обработкой исключений, а разработчики утверждали, что он запутывает поток управления.[16] Позже подобное исключение паника/восстанавливаться В язык был добавлен механизм, который авторы Go советуют использовать только для неисправимых ошибок, которые должны остановить весь процесс.[17][18][19][20]

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

Поддержка исключений в языках программирования

Многие компьютерные языки имеют встроенную поддержку исключений и обработки исключений. Это включает в себя ActionScript, Ада, BlitzMax, C ++, C #, Clojure, КОБОЛ, D, ECMAScript, Эйфель, Ява, ML, Оболочка нового поколения, Object Pascal (например. Delphi, Free Pascal, и тому подобное), PowerBuilder, Цель-C, OCaml, PHP (начиная с версии 5), PL / I, PL / SQL, Пролог, Python, REALbasic, Рубин, Scala, Семя7, Болтовня, Tcl, Визуальный пролог и большинство .СЕТЬ языков. Обработка исключений на этих языках обычно не возобновляется, и когда возникает исключение, программа выполняет обратный поиск через куча вызовов функций, пока не будет найден обработчик исключения.

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

Языки обработки исключений без этой раскрутки Common Lisp с этими Система условий, PL / I и Болтовня. Все вызывают обработчик исключений и не раскручивают стек; однако в PL / I, если "ON unit" (обработчик исключений) выполняет ИДТИ К вне блока ON, который размотает стек. Обработчик исключений имеет возможность перезапустить вычисление, возобновить или прекратить выполнение. Это позволяет программе продолжить вычисления в том же месте, где произошла ошибка (например, когда ранее отсутствующий файл стал доступен), или реализовать уведомления, ведение журнала, запросы и переменные жидкости поверх механизма обработки исключений (как сделано в Smalltalk). Бесстековая реализация Мифрил язык программирования поддерживает постоянную обработку исключений без раскрутки стека.

За исключением незначительных синтаксических различий, используется всего несколько стилей обработки исключений. В самом популярном стиле исключение инициируется специальным оператором (бросать или же поднимать) с объектом исключения (например, с Java или Object Pascal) или значением специального расширяемого перечислимого типа (например, с Ada или SML). Область действия обработчиков исключений начинается с предложения маркера (пытаться или стартер блока языка, такой как начинать) и заканчивается началом первого предложения обработчика (ловить, Кроме, спасать). За этим может следовать несколько предложений обработчика, и в каждом можно указать, какие типы исключений он обрабатывает и какое имя использует для объекта исключения.

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

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

В целом код обработки исключений может выглядеть так (в Ява -подобно псевдокод ):

пытаться {    линия = консоль.readLine();    если (линия.длина() == 0) {        бросать новый EmptyLineException("Строка, прочитанная с консоли, пуста!");    }    консоль.printLine("Привет,% s!" % линия);    консоль.printLine(«Программа успешно запустилась».);}ловить (EmptyLineException е) {    консоль.printLine("Привет!");}ловить (Исключение е) {    консоль.printLine("Ошибка: " + е.сообщение());}наконец-то {    консоль.printLine(«Программа завершается».);}

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

Согласно статье 2008 года Вестли Веймер и Джордж Некула, синтаксис пытаться...наконец-то блоки в Java - фактор, способствующий дефектам программного обеспечения. Когда методу необходимо обрабатывать получение и высвобождение 3-5 ресурсов, программисты явно не желают вкладывать достаточно блоков из-за проблем с читабельностью, даже если это было бы правильным решением. Возможно использование одного пытаться...наконец-то блокировать даже при работе с несколькими ресурсами, но это требует правильного использования дозорные ценности, что является еще одним распространенным источником ошибок для этого типа проблем.[15]:8:6–8:7 Что касается семантики пытаться...ловить...наконец-то В целом, Веймер и Некула пишут, что «Хотя try-catch-finally концептуально прост, он имеет наиболее сложное описание выполнения в спецификации языка [Gosling et al. 1996] и требует четырех уровней вложенных« if »в своем официальное английское описание. Короче говоря, оно содержит большое количество угловые случаи программисты часто упускают из виду »[15]:8:13–8:14

C поддерживает различные средства проверки ошибок, но обычно не считается поддерживающим «обработку исключений», хотя то setjmp и longjmp стандартные библиотечные функции может использоваться для реализации семантики исключений.

Perl имеет дополнительную поддержку для структурированной обработки исключений.

Python поддержка обработки исключений является повсеместной и последовательной. Трудно написать надежную программу на Python, не используя ее пытаться и Кроме ключевые слова.[нужна цитата ]

Обработка исключений в иерархиях пользовательского интерфейса

Последние интерфейсные веб-фреймворки, такие как Реагировать и Vue, ввели механизмы обработки ошибок, при которых ошибки распространяются вверх по иерархии компонентов пользовательского интерфейса, аналогично тому, как ошибки распространяются вверх по стеку вызовов при выполнении кода.[21][22] Здесь механизм границы ошибок служит аналогом типичного механизма try-catch. Таким образом, компонент может гарантировать, что ошибки от его дочерних компонентов будут обнаружены и обработаны, а не распространены на родительские компоненты.

Например, во Vue компонент будет обнаруживать ошибки, реализовав errorCaptured

Vue.компонент('родитель', {    шаблон: '
'
, errorCaptured: (ошибаться, vm, Информация) => тревога('Произошла ошибка');})Vue.компонент('ребенок', { шаблон: '
{{cause_error ()}}
'
})

При таком использовании в разметке:

<родитель>    <ребенок></ребенок></родитель>

Ошибка, вызванная дочерним компонентом, перехватывается и обрабатывается родительским компонентом.[23]

Реализация обработки исключений

Реализация обработки исключений в языках программирования обычно требует значительной поддержки как со стороны генератора кода, так и со стороны система времени выполнения сопровождающий компилятор. (Это было добавление обработки исключений в C ++, которое закончило полезный срок службы исходного компилятора C ++, Cfront.[24]) Наиболее распространены две схемы. Первый, динамическая регистрация, генерирует код, который постоянно обновляет структуры о состоянии программы с точки зрения обработки исключений.[25] Обычно это добавляет новый элемент в макет кадра стека который знает, какие обработчики доступны для функции или метода, связанных с этим фреймом; если выбрасывается исключение, указатель в макете направляет среду выполнения на соответствующий код обработчика. Этот подход компактен с точки зрения места, но добавляет накладные расходы на выполнение при входе и выходе из кадра. Он обычно использовался во многих реализациях Ada, например, где сложная поддержка генерации и выполнения уже требовалась для многих других языковых функций. Динамическую регистрацию довольно просто определить, но она поддается доказательство правильности.[26]

Вторая схема, реализованная во многих компиляторах C ++ производственного качества, представляет собой настольный подход. Это создает статические таблицы в время компиляции и время ссылки которые относятся к диапазонам счетчик команд в состояние программы в отношении обработки исключений.[27] Затем, если выбрасывается исключение, система времени выполнения ищет текущее расположение инструкции в таблицах и определяет, какие обработчики задействованы и что необходимо сделать. Такой подход сводит к минимуму административные издержки в случае, если исключение не генерируется. Это происходит за счет некоторого пространства, но это пространство может быть выделено в специальные разделы данных, предназначенные только для чтения, которые не загружаются или не перемещаются до тех пор, пока фактически не будет создано исключение.[28] Этот второй подход также является лучшим с точки зрения достижения безопасность потоков[нужна цитата ].

Также были предложены другие схемы определения и реализации.[29] Для языков, поддерживающих метапрограммирование были продвинуты подходы, которые вообще не предполагают накладных расходов (помимо уже существующей поддержки рефлексии).[30]

Обработка исключений на основе проекта по контракту

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

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

«Принцип безопасной обработки исключений», представленный Бертраном Мейером в Построение объектно-ориентированного программного обеспечения затем утверждает, что существует только два значимых способа реагирования подпрограммы при возникновении исключения:

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

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

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

Отправить (м: СООБЩЕНИЕ) является  - Если возможно, отправьте m по быстрой ссылке, в противном случае - по медленной.местный  try_fast, try_slow: BOOLEANделать  если try_fast тогда     try_slow := Истинный     send_slow (м)  еще     try_fast := Истинный     send_fast (м)  конецспасать  если нет try_slow тогда     повторить попытку  конецконец

Логические локальные переменные инициализируются значением False в начале. Если send_fast выходит из строя, тело (делать предложение) будет выполняться снова, вызывая выполнение send_slow. Если это исполнение send_slow терпит неудачу, спасать предложение будет выполняться до конца без повторить попытку (нет еще пункт в финале если), что приводит к сбою выполнения подпрограммы в целом.

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

Хотя исключения в Eiffel имеют довольно ясную философию, Кинири (2006) критикует их реализацию, потому что «Исключения, которые являются частью определения языка, представлены значениями INTEGER, а определяемые разработчиком исключения - значениями STRING. [...] Кроме того, потому что они являются базовыми значениями, а не объектами, они не имеют внутренней семантики, кроме той, которая выражена во вспомогательной подпрограмме, которая обязательно не может быть надежной из-за действующей перегрузки представления (например, невозможно различить два целых числа одного и того же значения) ».[7]

Неперехваченные исключения

Если исключение выброшено, но не перехвачено (с точки зрения работы исключение выбрасывается, когда не указан применимый обработчик), неперехваченное исключение обрабатывается средой выполнения; процедура, которая делает это, называется обработчик неперехваченных исключений.[31][32] Наиболее распространенным поведением по умолчанию является завершение программы и вывод на консоль сообщения об ошибке, обычно включающего отладочную информацию, такую ​​как строковое представление исключения и трассировки стека.[31][33][34] Этого часто можно избежать, имея обработчик верхнего уровня (уровня приложения) (например, в цикл событий ), который перехватывает исключения до того, как они достигнут среды выполнения.[31][35]

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

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

Этот обработчик неперехваченных исключений по умолчанию может быть переопределен либо глобально, либо для каждого потока, например, для обеспечения альтернативного ведения журнала или отчетов конечного пользователя о неперехваченных исключениях или для перезапуска потоков, которые завершаются из-за неперехваченного исключения. Например, в Java это делается для одного потока через Thread.setUncaughtExceptionHandler и во всем мире через Thread.setDefaultUncaughtExceptionHandler; в Python это делается путем изменения sys.excepthook.

Статическая проверка исключений

Проверенные исключения

Разработчики Java разработали[36] проверенные исключения,[37] которые представляют собой особый набор исключений. Проверенные исключения, которые может вызвать метод, являются частью метода подпись. Например, если метод может выдать IOException, он должен явно объявить этот факт в своей сигнатуре метода. В противном случае возникает ошибка времени компиляции.

Кинири (2006) отмечает, однако, что библиотеки Java (как они были в 2006 году) часто были непоследовательными в своем подходе к сообщению об ошибках, потому что «не все ошибочные ситуации в Java представлены исключениями. Многие методы возвращают специальные значения, которые указывают на ошибку, закодированную как постоянное поле связанных классов ".[7]

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

В Язык программирования CLU имел функцию с интерфейсом, более близкую к тому, что Java представила позже. Функция может вызывать только исключения, перечисленные в ее типе, но любые утечки исключений из вызываемых функций автоматически превращаются в единственное исключение времени выполнения, отказ, вместо того, чтобы приводить к ошибке времени компиляции. Потом, Модула-3 имел аналогичную особенность.[39] Эти функции не включают проверку времени компиляции, которая является центральной в концепции проверенных исключений, и не были (по состоянию на 2006 год) включены в основные языки программирования, кроме Java.[40]

Ранние версии языка программирования C ++ включали дополнительный механизм для проверенных исключений, называемый спецификации исключения. По умолчанию любая функция может вызвать любое исключение, но это было ограничено бросать в сигнатуру функции добавлено предложение, в котором указывается, какие исключения может вызывать функция. Спецификации исключений не применялись во время компиляции. Нарушения привели к глобальной функции стандартное::непредвиденный называется.[41] Может быть дана пустая спецификация исключения, которая указывает, что функция не вызовет исключения. Это не было сделано по умолчанию, когда в язык была добавлена ​​обработка исключений, потому что это потребовало бы слишком большой модификации существующего кода, затруднило бы взаимодействие с кодом, написанным на других языках, и вызвало бы соблазн программистов написать слишком много обработчиков на локальном уровне. уровень.[41] Однако явное использование пустых спецификаций исключений может позволить компиляторам C ++ выполнять значительную оптимизацию кода и компоновки стека, которую обычно необходимо подавлять, когда обработка исключений может происходить в функции.[28] Некоторые аналитики считали труднодостижимым правильное использование спецификаций исключений в C ++.[42] В недавнем Стандарт языка C ++ (C ++ 11 ), это использование спецификаций исключений, как указано в С ++ 03 версия стандарта была устарел и был удален с языка в C ++ 17.[43] Функция, которая не будет генерировать никаких исключений, теперь может быть обозначена ключевым словом noexcept.

В отличие от Java, такие языки, как C #, не требуют объявления какого-либо типа исключения. Согласно Ханспетеру Мёссенбёку, отсутствие различия между вызываемыми (проверенными) исключениями и исключениями, которые не следует вызывать (непроверенными), делает написанную программу более удобной, но менее надежной, поскольку неперехваченное исключение приводит к прерыванию с ошибкой. трассировки стека.[44] Кинири (2006) отмечает, однако, что Java JDK (версия 1.4.1) выдает большое количество непроверенных исключений: одно на каждые 140 строк кода, тогда как Eiffel использует их гораздо реже, с одним из них на 4600 строк кода. Кинири также пишет: «Как известно любому программисту Java, объем попробуй поймать код в типичном Java-приложении иногда больше, чем сопоставимый код, необходимый для явной проверки формальных параметров и возвращаемого значения на других языках, в которых нет проверенных исключений. Фактически, среди опытных Java-программистов существует общее мнение, что работа с проверенными исключениями почти так же неприятна, как и написание документации. Таким образом, многие программисты сообщают, что они «возмущаются» отмеченными исключениями. Это приводит к обилию проверенных, но игнорируемых исключений ".[7] Кинири также отмечает, что разработчики C #, очевидно, находились под влиянием такого рода пользовательского опыта, и им приписывают следующую цитату (через Эрика Ганнерсона):

«Исследование небольших программ приводит к выводу, что требование спецификаций исключений может как повысить продуктивность разработчиков, так и улучшить качество кода, но опыт работы с крупными программными проектами предполагает другой результат - снижение производительности и незначительное повышение качества кода или его отсутствие».[7]

В соответствии с Андерс Хейлсберг в их группе разработчиков было довольно широкое согласие не проверять исключения в качестве языковой функции в C #. Хейлсберг объяснил в интервью, что

“The throws clause, at least the way it's implemented in Java, doesn't necessarily force you to handle the exceptions, but if you don't handle them, it forces you to acknowledge precisely which exceptions might pass through. It requires you to either catch declared exceptions or put them in your own throws clause. To work around this requirement, people do ridiculous things. For example, they decorate every method with, "throws Exception." That just completely defeats the feature, and you just made the programmer write more gobbledy gunk. That doesn't help anybody.”[45]

Views on usage

Checked exceptions can, at время компиляции, reduce the incidence of unhandled exceptions surfacing at время выполнения in a given application. Unchecked exceptions (such as the Ява объекты RuntimeException и Ошибка) remain unhandled.

However, checked exceptions can either require extensive бросает declarations, revealing implementation details and reducing инкапсуляция, or encourage coding poorly considered пытаться/ловить blocks that can hide legitimate exceptions from their appropriate handlers.[нужна цитата ] Consider a growing кодовая база через некоторое время. An interface may be declared to throw exceptions X and Y. In a later version of the code, if one wants to throw exception Z, it would make the new code incompatible with the earlier uses. Furthermore, with the adapter pattern, in which one body of code declares an interface that is then implemented by a different body of code so that code can be plugged in and called by the first, the adapter code may have a rich set of exceptions to describe problems, but is forced to use the exception types declared in the interface.

It is possible to reduce the number of declared exceptions either by declaring a superclass of all potentially thrown exceptions, or by defining and declaring exception types that are suitable for the level of abstraction of the called method[46] and mapping lower level exceptions to these types, preferably wrapped using exception chaining in order to preserve the root cause. In addition, it's very possible that in the example above of the changing interface that the calling code would need to be modified as well, since in some sense the exceptions a method may throw are part of the method's implicit interface anyway.

Используя бросает Исключение declaration or ловить (Исключение е) is usually sufficient for satisfying the checking in Java. While this may have some use, it essentially circumvents the checked exception mechanism, which Oracle discourages.[47]

Unchecked exception types should generally not be handled, except possibly at the outermost levels of scope. These often represent scenarios that do not allow for recovery: RuntimeExceptions frequently reflect programming defects,[48] и Ошибкаs generally represent unrecoverable JVM failures. Even in a language that supports checked exceptions, there are cases where the use of checked exceptions is not appropriate.[49]

Dynamic checking of exceptions

The point of exception handling routines is to ensure that the code can handle error conditions. In order to establish that exception handling routines are sufficiently robust, it is necessary to present the code with a wide spectrum of invalid or unexpected inputs, such as can be created via software введение неисправности и mutation testing (that is also sometimes referred to as нечеткое тестирование ). One of the most difficult types of software for which to write exception handling routines is protocol software, since a robust protocol implementation must be prepared to receive input that does not comply with the relevant specification(s).

In order to ensure that meaningful regression analysis can be conducted throughout a software development lifecycle process, any exception handling testing should be highly automated, and the test cases must be generated in a scientific, repeatable fashion. Several commercially available systems exist that perform such testing.

In runtime engine environments such as Ява или же .СЕТЬ, there exist tools that attach to the runtime engine and every time that an exception of interest occurs, they record debugging information that existed in memory at the time the exception was thrown (стек вызовов и куча значения). Эти инструменты называются automated exception handling or error interception tools and provide 'root-cause' information for exceptions.

Exception synchronicity

Somewhat related with the concept of checked exceptions is exception synchronicity. Synchronous exceptions happen at a specific program statement whereas asynchronous exceptions can raise practically anywhere.[50][51] It follows that asynchronous exception handling can't be required by the compiler. They are also difficult to program with. Examples of naturally asynchronous events include pressing Ctrl-C to interrupt a program, and receiving a сигнал such as "stop" or "suspend" from another thread of execution.

Programming languages typically deal with this by limiting asynchronicity, for example Java has deprecated the use of its ThreadDeath exception that was used to allow one thread to stop another one.[52] Instead, there can be semi-asynchronous exceptions that only raise in suitable locations of the program or synchronously.

Condition systems

Common Lisp, Дилан и Болтовня есть condition system[53] (видеть Common Lisp Condition System ) that encompasses the aforementioned exception handling systems. In those languages or environments the advent of a condition (a "generalisation of an error" according to Кент Питман ) implies a function call, and only late in the exception handler the decision to unwind the stack may be taken.

Conditions are a generalization of exceptions. When a condition arises, an appropriate condition handler is searched for and selected, in stack order, to handle the condition. Conditions that do not represent errors may safely go unhandled entirely; their only purpose may be to propagate hints or warnings toward the user.[54]

Continuable exceptions

This is related to the so-called resumption model of exception handling, in which some exceptions are said to be continuable: it is permitted to return to the expression that signaled an exception, after having taken corrective action in the handler. The condition system is generalized thus: within the handler of a non-serious condition (a.k.a. continuable exception), it is possible to jump to predefined restart points (a.k.a. restarts) that lie between the signaling expression and the condition handler. Restarts are functions closed over some lexical environment, allowing the programmer to repair this environment before exiting the condition handler completely or unwinding the stack even partially.

Примером может служить ENDPAGE condition in PL/I; the ON unit might write page trailer lines and header lines for the next page, then fall through to resume execution of the interrupted code.

Restarts separate mechanism from policy

Condition handling moreover provides a separation of mechanism from policy. Restarts provide various possible mechanisms for recovering from error, but do not select which mechanism is appropriate in a given situation. That is the province of the condition handler, which (since it is located in higher-level code) has access to a broader view.

An example: Suppose there is a library function whose purpose is to parse a single системный журнал file entry. What should this function do if the entry is malformed? There is no one right answer, because the same library could be deployed in programs for many different purposes. In an interactive log-file browser, the right thing to do might be to return the entry unparsed, so the user can see it—but in an automated log-summarizing program, the right thing to do might be to supply null values for the unreadable fields, but abort with an error, if too many entries have been malformed.

That is to say, the question can only be answered in terms of the broader goals of the program, which are not known to the general-purpose library function. Nonetheless, exiting with an error message is only rarely the right answer. So instead of simply exiting with an error, the function may establish restarts offering various ways to continue—for instance, to skip the log entry, to supply default or null values for the unreadable fields, to ask the user for the missing values, или же to unwind the stack and abort processing with an error message. The restarts offered constitute the механизмы available for recovering from error; the selection of restart by the condition handler supplies the политика.

Смотрите также

Рекомендации

  1. ^ "Hardware Exceptions Detection". TEXAS INSTRUMENTS. 2011-11-24. Архивировано из оригинал на 2013-11-10. Получено 2012-10-05.
  2. ^ а б Xiaoye Li; James Demmel (1994). "Faster Numerical Algorithms via Exception Handling, IEEE Transactions on Computers, 43(8)": 983–992. Цитировать журнал требует | журнал = (помощь)
  3. ^ а б c W.Kahan (July 5, 2005). "A Demonstration of Presubstitution for ∞/∞" (PDF). В архиве (PDF) from the original on March 10, 2012.
  4. ^ John Hauser (1996). "Handling Floating-Point Exceptions in Numeric Programs, ACM Transactions on Programming Languages and Systems 18(2)": 139–174. Цитировать журнал требует | журнал = (помощь)
  5. ^ "The lessons of Ariane". www.irisa.fr. В архиве from the original on 4 June 2016. Получено 5 мая 2018.
  6. ^ "ARIANE 5 Failure - Full Report". В архиве from the original on 2014-04-26. Получено 2014-07-16.
  7. ^ а б c d е ж Kiniry, J. R. (2006). "Exceptions in Java and Eiffel: Two Extremes in Exception Design and Application". Advanced Topics in Exception Handling Techniques. Конспект лекций по информатике. 4119. pp. 288–300. Дои:10.1007/11818502_16. ISBN  978-3-540-37443-5.
  8. ^ "Stroustrup: C++ Style and Technique FAQ". www.stroustrup.com. В архиве из оригинала 2 февраля 2018 г.. Получено 5 мая 2018.
  9. ^ All Exceptions Are Handled, Jim Wilcox, "All Exceptions Are Handled". В архиве из оригинала 18.03.2015. Получено 2014-12-08.
  10. ^ а б Gabriel & Steele 2008, п. 3.
  11. ^ White 1979, п. 194.
  12. ^ а б Stroustrup 1994, п. 392.
  13. ^ а б Stroustrup 1994, 16.6 Exception Handling: Resumption vs. Termination, pp. 390–393.
  14. ^ МАШИНА. Hoare. "The Emperor's Old Clothes". 1980 Turing Award Lecture
  15. ^ а б c d Weimer, W; Necula, G.C. (2008). "Exceptional Situations and Program Reliability" (PDF). Транзакции ACM по языкам и системам программирования. 30 (2). В архиве (PDF) from the original on 2015-09-23.
  16. ^ "Часто задаваемые вопросы". В архиве from the original on 2017-05-03. Получено 2017-04-27. We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional.
  17. ^ Panic And Recover В архиве 2013-10-24 на Wayback Machine, Go wiki
  18. ^ "Weekly Snapshot History". golang.org. В архиве from the original on 2017-04-03.
  19. ^ "Proposal for an exception-like mechanism". golang-nuts. 25 марта 2010 г.. Получено 25 марта 2010.
  20. ^ "Effective Go". golang.org. В архиве from the original on 2015-01-06.
  21. ^ "Error Boundaries". Реагировать. Получено 2018-12-10.
  22. ^ "Vue.js API". Vue.js. Получено 2018-12-10.
  23. ^ "Error handling with Vue.js". CatchJS. Получено 2018-12-10.
  24. ^ Скотт Мейерс, The Most Important C++ Software...Ever В архиве 2011-04-28 на Wayback Machine, 2006
  25. ^ D. Cameron, P. Faust, D. Lenkov, M. Mehta, "A portable implementation of C++ exception handling", Proceedings of the C++ Conference (August 1992) USENIX.
  26. ^ Graham Hutton, Joel Wright, "Compiling Exceptions Correctly В архиве 2014-09-11 в Wayback Machine ". Proceedings of the 7th International Conference on Mathematics of Program Construction, 2004.
  27. ^ Lajoie, Josée (March–April 1994). "Exception handling – Supporting the runtime mechanism". Отчет C ++. 6 (3).
  28. ^ а б Schilling, Jonathan L. (August 1998). "Optimizing away C++ exception handling". Уведомления SIGPLAN. 33 (8): 40–47. Дои:10.1145/286385.286390.
  29. ^ "«Архивная копия». Архивировано из оригинал на 2012-01-01. Получено 2012-02-27.CS1 maint: заархивированная копия как заголовок (связь)", Intel Corporation.
  30. ^ M. Hof, H. Mössenböck, P. Pirkelbauer, "Zero-Overhead Exception Handling Using Metaprogramming В архиве 2016-03-03 в Wayback Machine ", Proceedings SOFSEM'97, November 1997, Lecture Notes in Computer Science 1338, pp. 423-431.
  31. ^ а б c Mac Developer Library, "Uncaught Exceptions В архиве 2016-03-04 в Wayback Machine "
  32. ^ MSDN, AppDomain.UnhandledException Event В архиве 2016-03-04 в Wayback Machine
  33. ^ Учебник по Python, "8. Errors and Exceptions В архиве 2015-09-01 at the Wayback Machine "
  34. ^ "Java Practices -> Provide an uncaught exception handler". www.javapractices.com. В архиве from the original on 9 September 2016. Получено 5 мая 2018.
  35. ^ PyMOTW (Python Module Of The Week), "Exception Handling В архиве 2015-09-15 на Wayback Machine "
  36. ^ "Google Answers: The origin of checked exceptions". В архиве из оригинала 2011-08-06. Получено 2011-12-15.
  37. ^ Java Language Specification, chapter 11.2. http://java.sun.com/docs/books/jls/third_edition/html/exceptions.html#11.2 В архиве 2006-12-08 в Wayback Machine
  38. ^ "OcamlExc - An uncaught exceptions analyzer for Objective Caml". Caml.inria.fr. В архиве из оригинала 2011-08-06. Получено 2011-12-15.
  39. ^ "Modula-3 - Procedure Types". .cs.columbia.edu. 1995-03-08. В архиве из оригинала 2008-05-09. Получено 2011-12-15.
  40. ^ "Bruce Eckel's MindView, Inc: Does Java need Checked Exceptions?". Mindview.net. Архивировано из оригинал on 2002-04-05. Получено 2011-12-15.
  41. ^ а б Бьярне Страуструп, Язык программирования C ++ Third Edition, Эддисон Уэсли, 1997. ISBN  0-201-88954-4. pp. 375-380.
  42. ^ Reeves, J.W. (July 1996). "Ten Guidelines for Exception Specifications". Отчет C ++. 8 (7).
  43. ^ Sutter, Herb (3 марта 2010 г.). "Trip Report: March 2010 ISO C++ Standards Meeting". В архиве из оригинала 23 марта 2010 г.. Получено 24 марта 2010.
  44. ^ Mössenböck, Hanspeter (2002-03-25). "Advanced C#: Variable Number of Parameters" (PDF). http://ssw.jku.at/Teaching/Lectures/CSharp/Tutorial/: Institut für Systemsoftware, Johannes Kepler Universität Linz, Fachbereich Informatik. п. 32. В архиве (PDF) from the original on 2011-09-20. Получено 2011-08-05.
  45. ^ Bill Venners; Bruce Eckel (18 августа 2003 г.). "The Trouble with Checked Exceptions: A Conversation with Anders Hejlsberg, Part II". п.2. В архиве from the original on February 18, 2015.
  46. ^ Bloch 2001:178 Bloch, Joshua (2001). Effective Java Programming Language Guide. Addison-Wesley Professional. ISBN  978-0-201-31005-4.
  47. ^ "Advantages of Exceptions (The Java™ Tutorials : Essential Classes : Exceptions)". Download.oracle.com. В архиве from the original on 2011-10-26. Получено 2011-12-15.
  48. ^ Bloch 2001:172
  49. ^ "Unchecked Exceptions – The Controversy (The Java™ Tutorials : Essential Classes : Exceptions)". Download.oracle.com. В архиве из оригинала от 17.11.2011. Получено 2011-12-15.
  50. ^ "Asynchronous Exceptions in Haskell - Marlow, Jones, Moran (ResearchIndex)". Citeseer.ist.psu.edu. В архиве из оригинала от 23.02.2011. Получено 2011-12-15.
  51. ^ Safe asynchronous exceptions for Python. «Архивная копия». В архиве from the original on 2006-08-30. Получено 2006-12-07.CS1 maint: заархивированная копия как заголовок (связь)
  52. ^ "Java Thread Primitive Deprecation". Java.sun.com. В архиве из оригинала 26.04.2009. Получено 2011-12-15.
  53. ^ What Conditions (Exceptions) are Really About (2008-03-24). "What Conditions (Exceptions) are Really About". Danweinreb.org. Архивировано из оригинал 1 февраля 2013 г.. Получено 2014-09-18.
  54. ^ "Condition System Concepts". Franz.com. 2009-07-21. Архивировано из оригинал на 2007-06-28. Получено 2011-12-15.

внешняя ссылка