Обработка исключений (программирование)
В компьютерном программировании существует несколько языковых механизмов для обработки исключений . Термин «исключение» обычно используется для обозначения структуры данных, хранящей информацию об исключительном состоянии. Один из механизмов передачи управления или возникновения исключения известен как throw ; Говорят, что исключение выброшено . Исполнение передается в catch .
Использование
[ редактировать ]Языки программирования существенно различаются в понимании того, что такое исключение. Исключения можно использовать для представления и обработки ненормальных, непредсказуемых и ошибочных ситуаций, а также в качестве структур управления потоком данных для обработки обычных ситуаций. Например, итераторы Python выдают исключения StopIteration, чтобы сигнализировать о том, что итератор больше не создает элементов. [1] Во многих языках существуют разногласия относительно того, что представляет собой идиоматическое использование исключений. Например, Джошуа Блох утверждает, что исключения Java следует использовать только в исключительных ситуациях. [2] но Кинири отмечает, что встроенная в Java FileNotFoundException
вовсе не является исключительным событием. [3] Аналогичным образом, Бьёрн Страуструп, автор C++, утверждает, что исключения C++ следует использовать только для обработки ошибок, поскольку именно для этого они и были разработаны. [4] но Кинири отмечает, что многие современные языки, такие как Ада, C++,
Modula-3, ML и OCaml, Python и Ruby используют исключения для управления потоком. Некоторые языки, такие как Eiffel, C#, Common Lisp и Modula-2, предприняли согласованные усилия по ограничению использования исключений, хотя это делается на социальном, а не на техническом уровне. [3]
История
[ редактировать ]Программная обработка исключений была разработана в 1960-х и 1970-х годах. ЛИСП 1.5 (1958–1961) [5] разрешено делать исключения со стороны ERROR
псевдофункция, аналогично ошибкам, возникающим в интерпретаторе или компиляторе. Исключения были обнаружены ERRORSET
ключевое слово, которое вернуло NIL
в случае ошибки вместо завершения программы или входа в отладчик. [6]
Примерно в 1964 году PL/I представил свою собственную форму обработки исключений, позволяющую обрабатывать прерывания с помощью модулей ON. [7]
МакЛисп заметил, что ERRSET
и ERR
использовались не только для возникновения ошибок, но и для нелокального потока управления, поэтому были добавлены два новых ключевых слова: CATCH
и THROW
(июнь 1972 г.). [8] Поведение очистки, которое сейчас обычно называют «наконец-то», было введено в NIL (новая реализация LISP) в середине-конце 1970-х годов как UNWIND-PROTECT
. [9] Затем это было принято Common Lisp . Современником этому был dynamic-wind
в Scheme, который обрабатывал исключения в замыканиях. Первыми статьями по структурированной обработке исключений были Goodenough (1975a) и Goodenough (1975b) . [10] Обработка исключений впоследствии получила широкое распространение во многих языках программирования, начиная с 1980-х годов.
Синтаксис
[ редактировать ]Многие компьютерные языки имеют встроенную синтаксическую поддержку исключений и их обработку. Сюда входят ActionScript , Ada , BlitzMax , C++ , C# , Clojure , COBOL , D , ECMAScript , Eiffel , Java , ML , Object Pascal (например, Delphi , Free Pascal и тому подобное), PowerBuilder , Objective-C , OCaml , Perl, [11] PHP (начиная с версии 5), PL/I , PL/SQL , Prolog , Python , REALbasic , Ruby , Scala , Seed7 , Smalltalk , Tcl , Visual Prolog и большинство .NET языков .
За исключением незначительных синтаксических различий, используется всего несколько стилей обработки исключений. В наиболее популярном стиле исключение инициируется специальным оператором ( throw
или raise
) с объектом исключения (например, в Java или Object Pascal) или значением специального расширяемого перечислимого типа (например, в Ada или SML). Область действия обработчиков исключений начинается с предложения маркера ( try
или стартовый блок языка, например begin
) и заканчивается в начале первого предложения обработчика ( catch
, except
, rescue
). Далее могут следовать несколько предложений обработчика, каждое из которых может указывать, какие типы исключений оно обрабатывает и какое имя оно использует для объекта исключения. В качестве незначительного варианта некоторые языки используют одно предложение обработчика, которое внутренне обрабатывает класс исключения.
Также распространенным является связанное предложение ( finally
или ensure
), который выполняется независимо от того, возникло исключение или нет, обычно для освобождения ресурсов, полученных в теле блока обработки исключений. Примечательно, что C++ не предоставляет эту конструкцию, вместо этого рекомендуется метод инициализации сбора ресурсов (RAII), который освобождает ресурсы с помощью деструкторов . [12] Согласно статье Уэстли Веймера и Джорджа Некулы 2008 года , синтаксис try
... finally
блоков в Java является фактором, способствующим дефектам программного обеспечения. Когда методу необходимо обрабатывать получение и освобождение 3–5 ресурсов, программисты, очевидно, не хотят вкладывать достаточное количество блоков из-за проблем с читаемостью, даже если это было бы правильным решением. Возможно использование одного try
... finally
блокировать даже при работе с несколькими ресурсами, но это требует правильного использования контрольных значений , что является еще одним распространенным источником ошибок для проблем такого типа. [13] : 8:6–8:7
Python и Ruby также допускают использование предложения ( else
), который используется в случае, если до достижения конца области действия обработчика не возникло никаких исключений.
В целом код обработки исключений может выглядеть так (в Java -подобном псевдокоде ):
try {
line = console.readLine();
if (line.length() == 0) {
throw new EmptyLineException("The line read from console was empty!");
}
console.printLine("Hello %s!" % line);
}
catch (EmptyLineException e) {
console.printLine("Hello!");
}
catch (Exception e) {
console.printLine("Error: " + e.message());
}
else {
console.printLine("The program ran successfully.");
}
finally {
console.printLine("The program is now terminating.");
}
В C нет обработки исключений try-catch, но используются коды возврата для проверки ошибок . setjmp
и longjmp
функции стандартной библиотеки можно использовать для реализации обработки try-catch с помощью макросов. [14]
Использование Perl 5 die
для throw
и eval {} if ($@) {}
для попытки-пойма. Он имеет модули CPAN, которые предлагают семантику try-catch. [15]
Семантика прекращения и возобновления
[ редактировать ]При возникновении исключения программа выполняет поиск в стеке вызовов функций, пока не найдет обработчик исключения. Некоторые языки требуют разматывания стека по мере продвижения поиска. То есть, если функция f , содержащий обработчик H для исключения E , вызывает функцию g , который, в свою очередь, вызывает функцию h и исключение E встречается в h , тогда функции рука g может быть прекращено, и Ч в ж справится Э. Это называется семантикой завершения. С другой стороны, механизмы обработки исключений могут не развернуть стек при входе. [примечание 1] к обработчику исключений, предоставляя обработчику исключений возможность перезапустить вычисление, возобновить или завершить его. Это позволяет программе продолжать вычисления точно в том же месте, где произошла ошибка (например, когда ранее отсутствующий файл стал доступен) или реализовать уведомления, ведение журнала, запросы и изменяемые переменные поверх механизма обработки исключений (как это сделано в Смолтолке). Разрешение возобновления вычислений с того места, где они были остановлены, называется семантикой возобновления.
Существуют теоретические и конструктивные аргументы в пользу того или иного решения. Дискуссии по стандартизации C++ в 1989–1991 годах привели к окончательному решению использовать семантику завершения в C++. [16] Бьерн Страуструп называет презентацию Джима Митчелла ключевым моментом:
Джим использовал обработку исключений в полудюжине языков в течение 20 лет и был одним из первых сторонников семантики возобновления как один из главных разработчиков и разработчиков системы Xerox Cedar/Mesa . Его послание было
- «прекращение действия предпочтительнее возобновления; это не вопрос мнения, а вопрос многолетнего опыта. Возобновление соблазнительно, но недействительно».
Он подкрепил это утверждение опытом работы с несколькими операционными системами. Ключевым примером был Cedar/Mesa: он был написан людьми, которые любили и использовали возобновление, но после десяти лет использования в системе в полмиллиона строк осталось только одно использование возобновления – и это был контекстный запрос. Поскольку возобновление на самом деле не было необходимо для такого запроса контекста, они удалили его и обнаружили значительное увеличение скорости в этой части системы. В каждом случае, когда использовалось возобновление, за десять лет оно становилось проблемой, и его заменяла более подходящая конструкция. По сути, каждое использование возобновления представляло собой неспособность сохранить отдельные уровни абстракции непересекающимися. [10]
Языки обработки исключений с возобновлением включают Common Lisp с его системой условий , PL/I, Dylan, R , [17] и Смолток . Однако большинство новых языков программирования следуют за C++ и используют семантику завершения.
Реализация обработки исключений
[ редактировать ]Реализация обработки исключений в языках программирования обычно включает в себя значительную поддержку как со стороны генератора кода, так и со стороны системы времени выполнения, сопровождающей компилятор. (Именно добавление обработки исключений в C++ положило конец полезному сроку службы оригинального компилятора C++, Cfront . [18] ) Наиболее распространены две схемы. Первый, динамическая регистрация генерирует код, который постоянно обновляет структуры состояния программы с точки зрения обработки исключений. [19] Обычно это добавляет новый элемент в макет кадра стека , который знает, какие обработчики доступны для функции или метода, связанного с этим кадром; если генерируется исключение, указатель в макете направляет среду выполнения к соответствующему коду обработчика. Этот подход компактен с точки зрения пространства, но добавляет накладные расходы на выполнение при входе и выходе кадра. Например, он широко использовался во многих реализациях Ada, где для многих других функций языка уже требовалась сложная поддержка генерации и выполнения. Microsoft 32-битная структурированная обработка исключений (SEH) использует этот подход с отдельным стеком исключений. [20] Динамическую регистрацию, которую довольно просто определить, можно доказать правильность . [21]
Вторая схема, реализованная во многих компиляторах C++ промышленного качества и 64-битной версии Microsoft SEH , представляет собой табличный подход . Это создает статические таблицы во время компиляции и во время компоновки , которые связывают диапазоны счетчика программы с состоянием программы с учетом обработки исключений. [22] Затем, если генерируется исключение, система времени выполнения ищет текущее местоположение инструкции в таблицах и определяет, какие обработчики задействованы и что необходимо сделать. Этот подход сводит к минимуму исполнительные издержки в случае, когда исключение не генерируется. Это происходит за счет некоторого пространства, но это пространство может быть выделено в разделы данных специального назначения, доступные только для чтения, которые не загружаются и не перемещаются до тех пор, пока не будет фактически выдано исключение. [23] Местоположение (в памяти) кода для обработки исключения не обязательно должно находиться внутри (или даже рядом) с областью памяти, где хранится остальная часть кода функции. Таким образом, если выдается исключение, это снижает производительность, что примерно сравнимо с вызовом функции. [24] – может возникнуть, если необходимо загрузить/кэшировать необходимый код обработки исключений. Однако эта схема имеет минимальные затраты производительности, если не создается исключение. Поскольку исключения в C++ должны быть исключительными (то есть необычными/редкими) событиями, фраза «исключения с нулевой стоимостью» [примечание 2] иногда используется для описания обработки исключений в C++. Как и идентификация типа во время выполнения C++ (RTTI), исключения могут не соответствовать принципу нулевых затрат , поскольку реализация обработки исключений во время выполнения требует ненулевого объема памяти для таблицы поиска. [25] По этой причине обработка исключений (и RTTI) может быть отключена во многих компиляторах C++, что может быть полезно для систем с очень ограниченной памятью. [25] (например, встроенные системы ). Этот второй подход также превосходит другие с точки зрения обеспечения потокобезопасности. [ нужна ссылка ] .
Были предложены и другие схемы определений и реализации. Для языков, поддерживающих метапрограммирование подходы, вообще не предполагающие накладных расходов (кроме уже существующей поддержки отражения ). , были разработаны [26]
Обработка исключений на основе проектирования по контракту
[ редактировать ]Иной взгляд на исключения основан на принципах проектирования по контракту и поддерживается, в частности, языком Eiffel . Идея состоит в том, чтобы обеспечить более строгую основу для обработки исключений, точно определяя, что такое «нормальное» и «ненормальное» поведение. В частности, этот подход основан на двух концепциях:
- Неудача : неспособность операции выполнить свой контракт. Например, сложение может привести к арифметическому переполнению (оно не выполняет свой контракт по вычислению хорошего приближения к математической сумме); или подпрограмма может не выполнить свое постусловие.
- Исключение : ненормальное событие, происходящее во время выполнения процедуры (эта программа является « получателем » исключения) во время ее выполнения. Такое аномальное событие возникает в результате сбоя операции, вызванной подпрограммой.
«Принцип безопасной обработки исключений», представленный Бертраном Мейером в книге « Объектно-ориентированное создание программного обеспечения» , утверждает, что существует только два значимых способа, которыми подпрограмма может реагировать при возникновении исключения:
- Сбой или «организованная паника»: процедура фиксирует состояние объекта, восстанавливая инвариант (это «организованная» часть), а затем завершается сбоем (паника), вызывая исключение в вызывающем объекте (так что аномальное событие устраняется). не игнорируется).
- Повторить: процедура пробует алгоритм еще раз, обычно после изменения некоторых значений, чтобы следующая попытка имела больше шансов на успех.
В частности, простое игнорирование исключения не допускается; блок должен либо быть повторен и успешно завершен, либо передать исключение вызывающему объекту.
Вот пример, выраженный в синтаксисе Эйфеля. Предполагается, что рутина send_fast
обычно это лучший способ отправить сообщение, но он может завершиться неудачно, что вызовет исключение; если да, то алгоритм затем использует send_slow
, который будет выходить из строя реже. Если send_slow
не получается, рутина send
в целом должен завершиться ошибкой, в результате чего вызывающая сторона получит исключение.
send (m: MESSAGE) is
-- Send m through fast link, if possible, otherwise through slow link.
local
tried_fast, tried_slow: BOOLEAN
do
if tried_fast then
tried_slow := True
send_slow (m)
else
tried_fast := True
send_fast (m)
end
rescue
if not tried_slow then
retry
end
end
Логические локальные переменные вначале инициализируются значением False. Если send_fast
выходит из строя, кузов( do
пункт) будет выполнен снова, что приведет к выполнению send_slow
. Если это выполнение send_slow
терпит неудачу, rescue
предложение будет выполнено до конца без retry
(нет else
пункт в финале if
), что приводит к сбою выполнения процедуры в целом.
Достоинство этого подхода состоит в том, что он четко определяет, что такое «нормальные» и «ненормальные» случаи: ненормальным случаем, вызывающим исключение, является тот, в котором подпрограмма не может выполнить свой контракт. Он определяет четкое распределение ролей: do
пункт (обычный орган) отвечает за достижение или попытку достижения контракта подпрограммы; тот rescue
Предложение отвечает за восстановление контекста и перезапуск процесса, если есть шанс на успех, но не за выполнение каких-либо реальных вычислений.
Хотя исключения в Eiffel имеют довольно четкую философию, Кинири (2006) критикует их реализацию, потому что «Исключения, являющиеся частью определения языка, представлены значениями INTEGER, исключения, определенные разработчиком, значениями STRING. [...] Кроме того, потому что они являются базовыми значениями, а не объектами, они не имеют никакой внутренней семантики, кроме той, которая выражена во вспомогательной подпрограмме, которая обязательно не может быть надежной из-за фактической перегрузки представления (например, нельзя различать два целых числа одного и того же значения)». [3]
Неперехваченные исключения
[ редактировать ]Современные приложения сталкиваются со многими проблемами проектирования при рассмотрении стратегий обработки исключений. Особенно в современных приложениях уровня предприятия исключения часто должны пересекать границы процессов и машин. Частью разработки надежной стратегии обработки исключений является распознавание случаев сбоя процесса до такой степени, что его невозможно экономически обработать с помощью программной части процесса. [27]
Если исключение генерируется, но не перехватывается (в рабочем режиме исключение генерируется, когда не указан подходящий обработчик), неперехваченное исключение обрабатывается средой выполнения; процедура, которая это делает, называется обработчик неперехваченных исключений . [28] [29] Наиболее распространенным поведением по умолчанию является завершение программы и вывод на консоль сообщения об ошибке, обычно включающего отладочную информацию, такую как строковое представление исключения и трассировку стека . [28] [30] [31] Этого часто можно избежать, используя обработчик верхнего уровня (уровня приложения) (например, в цикле событий ), который перехватывает исключения до того, как они достигнут среды выполнения. [28] [32]
Обратите внимание, что даже несмотря на то, что неперехваченное исключение может привести к ненормальному завершению программы (программа может быть некорректной, если исключение не перехвачено, в частности, из-за того, что не выполняется откат частично завершенных транзакций или не высвобождаются ресурсы), процесс завершается нормально (при условии, что среда выполнения работает правильно), поскольку среда выполнения (которая контролирует выполнение программы) может обеспечить упорядоченное завершение процесса.
В многопоточной программе неперехваченное исключение в потоке может вместо этого привести к завершению только этого потока, а не всего процесса (неперехваченные исключения в обработчике уровня потока перехватываются обработчиком верхнего уровня). Это особенно важно для серверов, где, например, сервлет (работающий в своем собственном потоке) может быть завершен без общего воздействия на сервер.
Этот обработчик неперехваченных исключений по умолчанию может быть переопределен либо глобально, либо для каждого потока, например, чтобы обеспечить альтернативное ведение журнала или отчетность конечного пользователя о неперехваченных исключениях или перезапустить потоки, которые завершаются из-за неперехваченного исключения. Например, в Java это делается для одного потока через Thread.setUncaughtExceptionHandler
и по всему миру через Thread.setDefaultUncaughtExceptionHandler
; в Python это делается путем изменения sys.excepthook
.
Проверенные исключения
[ редактировать ]В Java введено понятие проверяемых исключений, [33] [34] которые представляют собой особые классы исключений. метода Проверенные исключения, которые может вызвать метод, должны быть частью сигнатуры . Например, если метод может выдать IOException
, он должен явно объявить этот факт в сигнатуре своего метода. Если этого не сделать, возникает ошибка времени компиляции. По мнению Ханспетера Мессенбека, проверяемые исключения менее удобны, но более надежны. [35] Проверенные исключения могут во время компиляции уменьшить количество необработанных исключений, возникающих во время выполнения данного приложения.
Кинири пишет: «Как известно любому Java-программисту, объем try catch
Код в типичном приложении Java иногда больше, чем сопоставимый код, необходимый для явной проверки формальных параметров и возвращаемого значения в других языках, которые не имеют проверяемых исключений. Фактически, среди опытных Java-программистов существует общее мнение, что работа с проверенными исключениями — почти такая же неприятная задача, как написание документации. Таким образом, многие программисты сообщают, что они «возмущаются» проверенными исключениями.». [3] Мартин Фаулер написал: «... в целом я думаю, что исключения — это хорошо, но исключения, проверяемые Java, приносят больше проблем, чем пользы». [36] По состоянию на 2006 год ни один основной язык программирования не последовал примеру Java в добавлении проверяемых исключений. [36] Например, C# не требует и не допускает объявления каких-либо спецификаций исключений, о чем написал Эрик Ганнерсон: [37] [3] [36]
«Изучение небольших программ приводит к выводу, что требование спецификаций исключений может как повысить производительность разработчиков, так и улучшить качество кода, но опыт крупных программных проектов подсказывает другой результат — снижение производительности и незначительное повышение качества кода или отсутствие его».
Андерс Хейлсберг описывает две проблемы, связанные с проверенными исключениями: [38]
- Управление версиями: метод может быть объявлен так, чтобы генерировать исключения X и Y. В более поздней версии кода нельзя генерировать исключение Z из метода, поскольку это сделает новый код несовместимым с предыдущим использованием. Проверенные исключения требуют, чтобы вызывающие методы либо добавляли Z в предложение throws, либо обрабатывали исключение. Альтернативно, Z может быть неверно представлен как X или Y.
- Масштабируемость. В иерархической структуре каждая система может иметь несколько подсистем. Каждая подсистема может генерировать несколько исключений. Каждая родительская система должна иметь дело с исключениями всех подсистем ниже нее, в результате чего приходится иметь дело с экспоненциальным числом исключений. Проверенные исключения требуют, чтобы все эти исключения обрабатывались явно.
Чтобы обойти эту проблему, говорит Хейлсберг, программисты прибегают к обходу этой функции с помощью throws Exception
декларация. Еще одним обходным путем является использование try { ... } catch (Exception e) {}
обработчик. [38] Это называется всеобъемлющей обработкой исключений или обработкой исключений Pokémon после ключевой фразы шоу «Надо поймать их всех!». [39] Учебные пособия по Java не рекомендуют перехватывать все исключения, поскольку они могут перехватывать исключения, «для которых обработчик не предназначен». [40] Еще один нежелательный способ обхода — сделать все исключения подклассами. RuntimeException
. [41] Рекомендуемое решение — использовать универсальный обработчик или предложение throws, но с конкретным суперклассом всех потенциально выбрасываемых исключений, а не с общим суперклассом. Exception
. Еще одно рекомендуемое решение — определить и объявить типы исключений, подходящие для уровня абстракции вызываемого метода. [42] и сопоставлять исключения нижнего уровня с этими типами с помощью цепочки исключений .
Подобные механизмы
[ редактировать ]Корни проверенных исключений уходят корнями в программирования CLU . понятие спецификации исключений языка [43] Функция может вызывать только исключения, перечисленные в ее типе, но любые утечки исключений из вызываемых функций автоматически превращаются в единственное исключение во время выполнения. failure
, вместо того, чтобы привести к ошибке времени компиляции. [44] Позже «Модулы-3» . аналогичная особенность появилась у [45] Эти функции не включают проверку во время компиляции, которая является центральной в концепции проверенных исключений. [43]
Ранние версии языка программирования C++ включали дополнительный механизм, аналогичный проверенным исключениям, называемый спецификациями исключений . По умолчанию любая функция может генерировать любое исключение, но это может быть ограничено throw
В сигнатуру функции добавлено предложение, в котором указывается, какие исключения может генерировать функция. Спецификации исключений не применялись во время компиляции. Нарушения привели к нарушению глобальной функции std::unexpected
будучи вызванным. [46] Можно было указать пустую спецификацию исключения, что указывало бы на то, что функция не будет генерировать никаких исключений. Это не было сделано по умолчанию, когда в язык была добавлена обработка исключений, поскольку это потребовало бы слишком большой модификации существующего кода, затруднило бы взаимодействие с кодом, написанным на других языках, и заставило бы программистов писать слишком много обработчиков на локальном компьютере. уровень. [46] Однако явное использование пустых спецификаций исключений может позволить компиляторам C++ выполнять значительную оптимизацию структуры кода и стека, которая невозможна, когда обработка исключений может происходить в функции. [23] Некоторые аналитики считали, что правильное использование спецификаций исключений в C++ труднодостижимо. [47] Такое использование спецификаций исключений было включено в C++98 и C++03 и признано устаревшим в стандарте языка C++ 2012 года ( C++11 ). [48] и был удален из языка C++17 . Функция, которая не будет генерировать никаких исключений, теперь может быть обозначена символом noexcept
ключевое слово.
существует анализатор неперехваченных исключений Для языка программирования OCaml . [49] Инструмент сообщает о наборе возникших исключений в виде сигнатуры расширенного типа. Но, в отличие от проверяемых исключений, инструмент не требует никаких синтаксических аннотаций и является внешним (т.е. можно скомпилировать и запустить программу без проверки исключений).
Динамическая проверка исключений
[ редактировать ]Целью процедур обработки исключений является обеспечение способности кода обрабатывать ошибочные ситуации. Чтобы установить, что процедуры обработки исключений достаточно надежны, необходимо представить код с широким спектром недопустимых или неожиданных входных данных, например, которые могут быть созданы с помощью внедрения ошибок программного обеспечения и тестирования мутаций (которое также иногда называют нечетким тестированием). тестирование ). Одним из наиболее сложных типов программного обеспечения, для которого необходимо писать процедуры обработки исключений, является программное обеспечение протоколов, поскольку надежная реализация протокола должна быть подготовлена к приему входных данных, которые не соответствуют соответствующим спецификациям.
Чтобы обеспечить проведение значимого регрессионного анализа на протяжении всего жизненного цикла разработки программного обеспечения , любое тестирование обработки исключений должно быть в высокой степени автоматизировано, а тестовые примеры должны создаваться научным и воспроизводимым способом. Существует несколько коммерчески доступных систем, которые выполняют такое тестирование.
В средах механизмов выполнения, таких как Java или .NET , существуют инструменты, которые подключаются к механизму выполнения и каждый раз, когда возникает интересующее исключение, они записывают отладочную информацию, которая существовала в памяти на момент создания исключения ( стек вызовов и куча) . ценности). Эти инструменты называются инструментами автоматической обработки исключений или инструментами перехвата ошибок и предоставляют информацию об «основных причинах» исключений.
Асинхронные исключения
[ редактировать ]Асинхронные исключения — это события, вызываемые отдельным потоком или внешним процессом, например нажатие Ctrl-C для прерывания программы, получение сигнала или отправка прерывающего сообщения, например «остановить» или «приостановить» из другого потока выполнения . [50] [51] В то время как синхронные исключения случаются в определенный throw
оператор, асинхронные исключения могут быть вызваны в любое время. Отсюда следует, что асинхронная обработка исключений не может быть оптимизирована компилятором, поскольку он не может доказать отсутствие асинхронных исключений. Их также сложно правильно запрограммировать, поскольку асинхронные исключения необходимо блокировать во время операций очистки, чтобы избежать утечек ресурсов.
Языки программирования обычно избегают или ограничивают асинхронную обработку исключений, например, C++ запрещает создание исключений из обработчиков сигналов, а в Java исключено использование исключения ThreadDeath, которое использовалось, чтобы позволить одному потоку остановить другой. [52] Еще одна особенность — полуасинхронный механизм, вызывающий асинхронное исключение только во время определенных операций программы. Например, Java Thread.interrupt()
влияет на поток только тогда, когда поток вызывает операцию, которая выдает InterruptedException
. [53] Аналогичный POSIX pthread_cancel
API имеет условия гонки, которые делают невозможным безопасное использование. [54]
Системы условий
[ редактировать ]Общий Лисп , R , [55] У Дилана и Smalltalk есть система условий. [56] (см. Система условий Common Lisp ), которая включает в себя вышеупомянутые системы обработки исключений. В этих языках или средах появление условия («обобщение ошибки» согласно Кенту Питману ) подразумевает вызов функции, и только на поздней стадии обработчика исключений может быть принято решение развернуть стек.
Условия представляют собой обобщение исключений. При возникновении условия выполняется поиск и выбор подходящего обработчика условий в порядке стека для обработки этого условия. Условия, которые не представляют собой ошибки, могут полностью остаться необработанными; их единственной целью может быть распространение подсказок или предупреждений пользователю. [57]
Продолжаемые исключения
[ редактировать ]Это связано с так называемой моделью возобновления обработки исключений, в которой некоторые исключения считаются продолжаемыми : разрешается вернуться к выражению, которое сигнализировало об исключении, после выполнения корректирующих действий в обработчике. Система условий обобщается таким образом: внутри обработчика несерьезного состояния (так называемого продолжаемого исключения ) можно перейти к предопределенным точкам перезапуска (также известным как перезапуски ), которые лежат между выражением сигнализации и обработчиком условия. Перезапуски — это функции, закрывающиеся в некоторой лексической среде, позволяющие программисту восстановить эту среду перед полным выходом из обработчика условий или даже частичным разматыванием стека.
Примером может служить условие ENDPAGE в PL/I; модуль ON может записать строки завершения страницы и строки заголовка для следующей страницы, а затем отказаться от выполнения, чтобы возобновить выполнение прерванного кода.
Перезапускает отдельный механизм из политики.
[ редактировать ]Более того, обработка условий обеспечивает отделение механизма от политики . Перезапуски предоставляют различные возможные механизмы восстановления после ошибки, но не выбирают, какой механизм подходит в данной ситуации. Это область действия обработчика условий, который (поскольку он расположен в коде более высокого уровня) имеет доступ к более широкому представлению.
Пример: предположим, что существует библиотечная функция, целью которой является анализ одной записи файла системного журнала . Что должна делать эта функция, если запись имеет неверный формат? Единого правильного ответа не существует, поскольку одну и ту же библиотеку можно использовать в программах для самых разных целей. В интерактивном браузере файлов журналов правильным решением может быть возврат записи в неанализированном виде, чтобы пользователь мог ее увидеть, но в программе автоматического суммирования журналов правильным решением может быть предоставление нулевых значений для нечитаемые поля, но прерывается с ошибкой, если слишком много записей имеют неверный формат.
То есть на вопрос можно ответить только с точки зрения более широких целей программы, которые не известны библиотечной функции общего назначения. Тем не менее, выход с сообщением об ошибке лишь в редких случаях является правильным ответом. Таким образом, вместо простого выхода с ошибкой функция может перезапуститься, предлагая различные способы продолжения, например, пропустить запись журнала, указать значения по умолчанию или нулевые значения для нечитаемых полей, запросить у пользователя недостающие значения или чтобы развернуть стек и прервать обработку с сообщением об ошибке. Предлагаемые перезапуски представляют собой механизмы восстановления после ошибки; выбор перезапуска обработчиком условий обеспечивает политику .
Критика
[ редактировать ]Обработка исключений часто неправильно обрабатывается в программном обеспечении, особенно при наличии нескольких источников исключений; Анализ потока данных 5 миллионов строк кода Java обнаружил более 1300 дефектов обработки исключений. [13] Ссылаясь на многочисленные предыдущие исследования других людей (1999–2004 гг.) и свои собственные результаты, Веймер и Некула написали, что серьезная проблема с исключениями заключается в том, что они «создают скрытые пути потока управления, о которых программистам трудно рассуждать». [13] : 8:27 «Хотя try-catch-finally концептуально прост, он имеет самое сложное описание выполнения в спецификации языка [Gosling et al. 1996] и требует четырех уровней вложенных «если» в своем официальном английском описании. Короче говоря, он содержит большое количество крайних случаев , которые программисты часто упускают из виду». [13] : 8:13–8:14
Исключения, такие как неструктурированный поток, увеличивают риск утечек ресурсов (например, выход из раздела, заблокированного мьютексом , или временного удержания файла открытым) или несогласованного состояния. Существуют различные методы управления ресурсами при наличии исключений, чаще всего сочетающие шаблон удаления с некоторой формой защиты от раскрутки (например, finally
пункт), который автоматически освобождает ресурс, когда элемент управления выходит из раздела кода.
Тони Хоар в 1980 году описал язык программирования Ada как имеющий «...множество функций и условных обозначений, многие из которых ненужны, а некоторые, такие как обработка исключений, даже опасны. [...] Не допускайте использования этого языка в его нынешнее состояние будет использоваться в приложениях, где надежность имеет решающее значение [...]. Следующая ракета, которая сбилась с пути в результате ошибки языка программирования, может быть не исследовательской космической ракетой в безобидном путешествии на Венеру: это может быть ядерная боеголовка взорвалась над одним из наших городов». [58]
Разработчики Go считают, что идиома try-catch-finally запутывает поток управления , [59] и ввел исключение, подобное исключению panic
/ recover
механизм. [60] recover()
отличается от catch
в том, что его можно вызвать только изнутри defer
блок кода в функции, поэтому обработчик может только выполнять очистку и изменять возвращаемые значения функции и не может возвращать управление в произвольную точку внутри функции. [61] defer
сам блок функционирует аналогично finally
пункт.
См. также
[ редактировать ]- Автоматическая обработка исключений
- Продолжение
- Оборонительное программирование
- Безопасность исключений
- Типы опций и типы результатов , альтернативные способы обработки ошибок в функциональном программировании без исключений
Примечания
[ редактировать ]- ^ Например, в PL/I обычный выход из обработчика исключений разматывает стек.
- ^ «Нулевая стоимость [обработки]» существует только в том случае, если не выдается исключение (хотя будет и стоимость памяти, поскольку память необходима для таблицы поиска). Возникновение исключения требует (потенциально значительных) затрат (то есть, если
throw
выполняется). Реализация обработки исключений также может ограничить возможные оптимизации компилятора .
Ссылки
[ редактировать ]- ^ «Встроенные исключения — документация Python 3.10.4» . docs.python.org . Проверено 17 мая 2022 г.
- ^ Блох, Джошуа (2008). «Пункт 57: Используйте исключения только в исключительных ситуациях» . Эффективная Java (Второе изд.). Аддисон-Уэсли. п. 241 . ISBN 978-0-321-35668-0 .
- ^ Jump up to: а б с д и Кинири, младший (2006). «Исключения в Java и Eiffel: две крайности в разработке и применении исключений». Расширенные темы по методам обработки исключений (PDF) . Конспекты лекций по информатике. Том. 4119. стр. 288–300. дои : 10.1007/11818502_16 . ISBN 978-3-540-37443-5 . S2CID 33283674 .
- ^ «Страуструп: Часто задаваемые вопросы по стилю и технике C++» . www.stroustrup.com . Архивировано из оригинала 2 февраля 2018 года . Проверено 5 мая 2018 г.
- ^ Маккарти, Джон (12 февраля 1979 г.). «История Лиспа» . www-formal.stanford.edu . Проверено 13 января 2022 г.
- ^ Маккарти, Джон; Левин, Майкл И.; Абрахамс, Пол В.; Эдвардс, Дэниел Дж.; Харт, Тимоти П. (14 июля 1961 г.). Руководство программиста LISP 1.5 (PDF) . Проверено 13 января 2022 г.
- ^ «Заявление ON» (PDF) . Операционная система IBM System/360, спецификации языка PL/I (PDF) . ИБМ. Июль 1966 г. с. 120. С28-6571-3.
- ^ Габриэль и Стил 2008 , с. 3.
- ^ Уайт 1979 , с. 194.
- ^ Jump up to: а б Страуструп 1994 , с. 392.
- ^ «Исключения — Документация по обработке исключений в Perl» .
- ^ Страуструп, Бьерн. «Часто задаваемые вопросы по стилю и технике C++» . www.stroustrup.com . Проверено 12 января 2022 г.
- ^ Jump up to: а б с д Веймер, В; Некула, GC (2008). «Исключительные ситуации и надежность программ» (PDF) . Транзакции ACM в языках и системах программирования . Том. 30, нет. 2. Архивировано (PDF) из оригинала 23 сентября 2015 г.
- ^ Робертс, Эрик С. (21 марта 1989 г.). «Реализация исключений в C» (PDF) . Центр системных исследований DEC . СРЦ-РР-40 . Проверено 4 января 2022 г.
{{cite journal}}
: Для цитирования журнала требуется|journal=
( помощь ) - ^ Кристиансен, Том; Торкингтон, Натан (2003). «10.12. Обработка исключений». Поваренная книга Perl (2-е изд.). Пекин: О'Рейли. ISBN 0-596-00313-7 .
- ^ Страуструп 1994 , 16.6 Обработка исключений: возобновление или прекращение, стр. 390–393.
- ^ «R: Обработка и восстановление состояний» . search.r-project.org . Проверено 5 декабря 2022 г.
- ^ Скотт Мейерс , Самое важное программное обеспечение C++... когда-либо. Архивировано 28 апреля 2011 г. в Wayback Machine , 2006 г.
- ^ Д. Кэмерон, П. Фауст, Д. Ленков, М. Мехта, «Переносимая реализация обработки исключений C++», Материалы конференции C++ (август 1992 г.) USENIX .
- ^ Питер Кляйсснер (14 февраля 2009 г.). «Обработка исключений Windows — Питер Кляйсснер» . Архивировано из оригинала 14 октября 2013 года . Проверено 21 ноября 2009 г. , структурированной обработки исключений на основе компилятора Раздел
- ^ Грэм Хаттон, Джоэл Райт, « Правильная компиляция исключений , заархивированная 11 сентября 2014 г. в Wayback Machine ». Материалы 7-й Международной конференции по математике построения программ , 2004 г.
- ^ Лажуа, Жозе (март – апрель 1994 г.). «Обработка исключений – Поддержка механизма выполнения». Отчет С++ . 6 (3).
- ^ Jump up to: а б Шиллинг, Джонатан Л. (август 1998 г.). «Оптимизация обработки исключений C++» . Уведомления SIGPLAN . 33 (8): 40–47. дои : 10.1145/286385.286390 . S2CID 1522664 .
- ^ «Современные лучшие практики C++ по обработке исключений и ошибок» . Майкрософт . 8 марта 2021 г. Проверено 21 марта 2022 г.
- ^ Jump up to: а б Страуструп, Бьярне (18 ноября 2019 г.). «Исключения и альтернативы C++» (PDF) . Проверено 23 марта 2022 г.
- ^ М. Хоф, Х. Мессенбёк, П. Пиркельбауэр, « Обработка исключений с нулевыми издержками с использованием метапрограммирования , архивировано 3 марта 2016 г. в Wayback Machine », Труды SOFSEM'97 , ноябрь 1997 г., Конспекты лекций по информатике 1338 , стр. 423-431.
- ^ Все исключения обрабатываются, Джим Уилкокс, «Все исключения обрабатываются» . 22 февраля 2008 г.
- ^ Jump up to: а б с Библиотека разработчика Mac , « Неперехваченные исключения, заархивированные 4 марта 2016 г. на Wayback Machine »
- ^ MSDN , Событие AppDomain.UnhandledException, заархивированное 4 марта 2016 г. на Wayback Machine.
- ^ Учебное пособие по Python , « 8. Ошибки и исключения. Архивировано 1 сентября 2015 г. на Wayback Machine ».
- ^ «Практика Java -> Предоставьте обработчик неперехваченных исключений» . www.javapractices.com . Архивировано из оригинала 9 сентября 2016 года . Проверено 5 мая 2018 г.
- ^ PyMOTW (Модуль недели Python), « Обработка исключений, заархивировано 15 сентября 2015 г. на Wayback Machine »
- ^ «Ответы Google: происхождение проверенных исключений» . Архивировано из оригинала 6 августа 2011 г. Проверено 15 декабря 2011 г.
- ^ Спецификация языка Java, глава 11.2. http://java.sun.com/docs/books/jls/ Third_edition /html/Exceptions.html#11.2 Архивировано 8 декабря 2006 г. в Wayback Machine.
- ^ Мессенбёк, Ханспетер (25 марта 2002 г.). «Расширенный C#: переменное количество параметров» (PDF) . Институт системного программного обеспечения, Университет Иоганна Кеплера в Линце, факультет компьютерных наук. п. 32. Архивировано (PDF) из оригинала 20 сентября 2011 г. Проверено 5 августа 2011 г.
- ^ Jump up to: а б с Экель, Брюс (2006). Мышление на Java (4-е изд.). Река Аппер-Седл, Нью-Джерси: Прентис-Холл. стр. 347–348. ISBN 0-13-187248-6 .
- ^ Ганнерсон, Эрик (9 ноября 2000 г.). «C# и спецификации исключений» . Архивировано из оригинала 1 января 2006 года.
- ^ Jump up to: а б Билл Веннерс; Брюс Экель (18 августа 2003 г.). «Проблема с проверенными исключениями: разговор с Андерсом Хейлсбергом, часть II» . Проверено 4 января 2022 г.
- ^ Джуно, Джош (31 мая 2017 г.). Рецепты Java 9: подход «проблема-решение» . Апресс. п. 226. ИСБН 978-1-4842-1976-8 .
- ^ «Преимущества исключений (Учебные пособия по Java™: Основные классы: Исключения)» . Скачать.oracle.com. Архивировано из оригинала 26 октября 2011 г. Проверено 15 декабря 2011 г.
- ^ «Непроверенные исключения – противоречие (Учебные пособия по Java™: Основные классы: Исключения)» . Скачать.oracle.com. Архивировано из оригинала 17 ноября 2011 г. Проверено 15 декабря 2011 г.
- ^ Блох 2001: 178 Блох, Джошуа (2001). Эффективное руководство по языку программирования Java . Аддисон-Уэсли Профессионал. ISBN 978-0-201-31005-4 .
- ^ Jump up to: а б «MindView, Inc. Брюса Экеля: нужны ли Java проверенные исключения?» . Mindview.net. Архивировано из оригинала 5 апреля 2002 г. Проверено 15 декабря 2011 г.
- ^ Лисков, Б.Х.; Снайдер, А. (ноябрь 1979 г.). «Обработка исключений в CLU» (PDF) . Транзакции IEEE по разработке программного обеспечения . СЭ-5 (6): 546–558. дои : 10.1109/TSE.1979.230191 . S2CID 15506879 . Проверено 19 декабря 2021 г.
- ^ «Модула-3 – Типы процедур» . .cs.columbia.edu. 08.03.1995. Архивировано из оригинала 9 мая 2008 г. Проверено 15 декабря 2011 г.
- ^ Jump up to: а б Бьёрн Страуструп , «Язык программирования C++, третье издание», Эддисон Уэсли , 1997. ISBN 0-201-88954-4 . стр. 375-380.
- ^ Ривз, JW (июль 1996 г.). «Десять рекомендаций по спецификациям исключений». Отчет С++ . 8 (7).
- ^ Саттер, Херб (3 марта 2010 г.). «Отчет о поездке: собрание по стандартам ISO C++, март 2010 г.» . Архивировано из оригинала 23 марта 2010 года . Проверено 24 марта 2010 г.
- ^ «OcamlExc — анализатор неперехваченных исключений для Objective Caml» . Caml.inria.fr. Архивировано из оригинала 6 августа 2011 г. Проверено 15 декабря 2011 г.
- ^ «Асинхронные исключения в Haskell — Марлоу, Джонс, Моран (ResearchIndex)» . Сайт Citeseer.ist.psu.edu. Архивировано из оригинала 23 февраля 2011 г. Проверено 15 декабря 2011 г.
- ^ Фройнд, Стивен Н.; Митчелл, Марк П. «Безопасные асинхронные исключения для Python» (PDF) . Проверено 4 января 2022 г.
{{cite journal}}
: Для цитирования журнала требуется|journal=
( помощь ) - ^ «Устарение примитива Java Thread» . Java.sun.com. Архивировано из оригинала 26 апреля 2009 г. Проверено 15 декабря 2011 г.
- ^ «Прерывания (Учебные пособия по Java™ > Основные классы Java > Параллелизм)» . docs.oracle.com . Проверено 5 января 2022 г.
- ^ Фелкер, Рич. «Отмена потока и утечка ресурсов» . ewontfix.com . Проверено 5 января 2022 г.
- ^ «R: Обработка и восстановление состояний» . search.r-project.org . Проверено 25 марта 2024 г.
- ^ Что на самом деле представляют собой условия (исключения) (24 марта 2008 г.). «Что на самом деле представляют собой условия (исключения)» . Danweinreb.org. Архивировано из оригинала 1 февраля 2013 года . Проверено 18 сентября 2014 г.
- ^ «9.1 Концепции системы условий» . Франц.com. 2022-07-25. Архивировано из оригинала 7 июня 2024 г. Проверено 7 июня 2024 г.
- ^ АВТОМОБИЛЬ Хоара. «Старая одежда императора». Лекция на премию Тьюринга 1980 года
- ^ «Часто задаваемые вопросы» . Архивировано из оригинала 3 мая 2017 г. Проверено 27 апреля 2017 г.
Мы считаем, что соединение исключений со структурой управления, как в идиоме try-catch-finally, приводит к запутанному коду. Это также побуждает программистов помечать слишком много обычных ошибок, таких как невозможность открытия файла, как исключительные.
- ^ Паника и восстановление. Архивировано 24 октября 2013 г. на Wayback Machine , Go wiki.
- ^ Бендерский, Эли (8 августа 2018 г.). «О пользе и неправильном использовании паники в Го» . Сайт Эли Бендерского . Проверено 5 января 2022 г.
Конкретное ограничение состоит в том, что восстановление можно вызвать только в блоке кода отсрочки, который не может возвращать управление в произвольную точку, а может только выполнять очистку и настраивать возвращаемые значения функции.
Цитируемые работы
[ редактировать ]- Габриэль, Ричард П .; Стил, Гай Л. (2008). Модель эволюции языка (PDF) . LISP50: Празднование 50-летия Lisp. стр. 1–10. дои : 10.1145/1529966.1529967 . ISBN 978-1-60558-383-9 .
- Гуденаф, Джон Б. (1975a). Структурированная обработка исключений . Материалы 2-го симпозиума ACM SIGACT-SIGPLAN по принципам языков программирования - POPL '75. стр. 204–224. дои : 10.1145/512976.512997 .
- Гуденаф, Джон Б. (1975). «Обработка исключений: проблемы и предлагаемые обозначения» (PDF) . Коммуникации АКМ . 18 (12): 683–696. CiteSeerX 10.1.1.122.7791 . дои : 10.1145/361227.361230 . S2CID 12935051 .
- Страуструп, Бьярн (1994). Проектирование и эволюция C++ (1-е изд.). Ридинг, Массачусетс: Аддисон-Уэсли. ISBN 0-201-54330-3 .
- Уайт, Джон Л. (май 1979 г.). НИЛ — Перспектива (PDF) . Материалы конференции пользователей Macsyma 1979 года.