Написание кода для обработки ошибок
Обычно при сигнализации об ошибке выполняющаяся команда завершается, и управление немедленно возвращается в цикл команд редактора Emacs. Можно перехватывать ошибки, возникающие в части программы, установив обработчик ошибок с помощью специальной формы condition-case. Простой пример:
(condition-case nil
(delete-file filename)
(error nil))
Эта форма удаляет файл с именем filename, перехватывая любые ошибки и возвращая nil, если ошибка возникает. (Для такого простого случая можно воспользоваться макросом ignore-errors; см. ниже.)
Конструкция condition-case часто используется для перехвата предсказуемых ошибок, например невозможности открыть файл при вызове insert-file-contents. Она также используется для перехвата совершенно непредсказуемых ошибок, например возникающих при вычислении программой выражения, введённого пользователем.
Второй аргумент condition-case называется защищённой формой. (В приведённом выше примере защищённая форма — это вызов delete-file.) Обработчики ошибок вступают в действие при начале выполнения этой формы и деактивируются, когда она возвращает управление. Они остаются активными всё это время. В частности, они действуют во время выполнения функций, вызванных этой формой, их подпрограмм и так далее. Это хорошо, поскольку, строго говоря, ошибки могут сигнализироваться только примитивами Lisp (включая signal и error), вызываемыми защищённой формой, а не самой защищённой формой.
Аргументы после защищённой формы — это обработчики. В каждом обработчике указано одно или несколько имён условий (которые являются символами), определяющих, какие ошибки он будет обрабатывать. Символ ошибки, указываемый при сигнализации об ошибке, также задаёт список имён условий. Обработчик применяется к ошибке, если у них есть общие имена условий. В приведённом выше примере есть один обработчик, в котором указано одно имя условия — error, охватывающее все ошибки.
При поиске подходящего обработчика проверяются все установленные обработчики, начиная с установленного последним. Поэтому, если две вложенные формы condition-case предлагают обработать одну и ту же ошибку, её обработает внутренняя из них.
Если ошибку обрабатывает какая-либо форма condition-case, это обычно препятствует запуску отладчика, даже если debug-on-error указывает, что при этой ошибке следует вызвать отладчик.
Чтобы иметь возможность отлаживать ошибки, перехваченные с помощью condition-case, присвойте переменной debug-on-signal значение, отличное от nil. Можно также указать, что конкретный обработчик должен сначала запустить отладчик, включив debug в список условий, например так:
(condition-case nil
(delete-file filename)
((debug error) nil))
В данном случае debug лишь предотвращает подавление вызова отладчика формой condition-case. Отладчик будет вызван при конкретной ошибке только в том случае, если debug-on-error и другие обычные механизмы фильтрации указывают на необходимость его запуска. См. Вход в отладчик при ошибке.
- Макрос: condition-case-unless-debug var protected-form handlers…
Макрос
condition-case-unless-debugпредоставляет ещё один способ управлять отладкой таких форм. Он ведёт себя точно так же, какcondition-case, если только переменнаяdebug-on-errorне имеет значения, отличного отnil; в этом случае он запускает отладчик Emacs до выполнения любого подходящего обработчика. (Если подходящий обработчик есть, он всё равно будет выполнен при выходе из отладчика.)
Как только Emacs определяет, что ошибку обрабатывает определённый обработчик, управление передаётся этому обработчику. Для этого Emacs снимает все привязки переменных, созданные конструкциями связывания, из которых происходит выход, и выполняет очистку всех форм unwind-protect, из которых происходит выход. После передачи управления обработчику его тело выполняется обычным образом.
После выполнения тела обработчика выполнение возвращается из формы condition-case. Поскольку защищённая форма полностью завершает выполнение до запуска обработчика, обработчик не может возобновить выполнение с места возникновения ошибки и не может проверить привязки переменных, созданные внутри защищённой формы. Он может только выполнить очистку и продолжить работу.
Сигнализация и обработка ошибок отчасти напоминают throw и catch (см. Явный нелокальный выход: catch и throw), но это совершенно разные механизмы. Ошибку нельзя перехватить с помощью catch, а throw нельзя обработать обработчиком ошибок (однако использование throw при отсутствии подходящего catch сигнализирует ошибку, которую можно обработать).
- Специальная форма: condition-case var protected-form handlers…
-
Эта специальная форма устанавливает обработчики ошибок handlers на время выполнения protected-form. Если protected-form выполняется без ошибок, возвращаемое ею значение становится значением формы
condition-case(если нет обработчика успешного завершения; см. ниже). В этом случаеcondition-caseне оказывает никакого действия. Формаcondition-caseимеет значение, когда во время выполнения protected-form возникает ошибка.Каждый из элементов handlers — это список вида
(conditions body…). Здесь conditions — имя условия ошибки, которую нужно обработать, или список имён условий (в который можно включитьdebug, чтобы разрешить запуск отладчика до обработчика). Имя условияtсоответствует любому условию. body — одно или несколько выражений Lisp, выполняемых при обработке ошибки этим обработчиком. Вот несколько примеров обработчиков:(error nil) (arith-error (message "Division by zero")) ((arith-error file-error) (message "Either division by zero or failure to open a file"))
Для каждой возникающей ошибки есть символ ошибки, описывающий её тип и задающий список имён условий (см. Символы ошибок и имена условий). Emacs ищет во всех активных формах
condition-caseобработчик, в котором указано одно или несколько этих имён условий; ошибку обрабатывает самая внутренняя подходящая формаcondition-case. Внутри этой формыcondition-caseошибку обрабатывает первый подходящий обработчик.После выполнения тела обработчика форма
condition-caseнормально возвращает значение последней формы в теле обработчика как итоговое значение.Аргумент var — это переменная. Форма
condition-caseне связывает эту переменную при выполнении protected-form, а делает это только при обработке ошибки. В этот момент она локально связывает var с описанием ошибки, также иногда называемым сведениями об ошибке, — списком с подробной информацией об ошибке. Описание ошибки имеет вид(error-symbol . data). Обработчик может обратиться к этому списку, чтобы решить, что делать. Например, если ошибка связана с невозможностью открыть файл, имя файла находится во втором элементе data — третьем элементе описания ошибки.Если var равно
nil, это означает, что никакая переменная не связывается. Тогда символ ошибки и связанные с ней данные недоступны обработчику.В особом случае один из элементов handlers может представлять собой список вида
(:success body…); в этом случае body выполняется, а var (если оно не равноnil) связывается с возвращаемым значением protected-form, когда это выражение завершается без ошибки.Иногда необходимо повторно сигнализировать об ошибке, перехваченной с помощью
condition-case, чтобы её обработал обработчик внешнего уровня. Это можно сделать так:(signal err)
где
err— переменная описания ошибки, первый аргументcondition-case, ошибку которого нужно сигнализировать повторно. См. Определение signal.
- Функция: error-type error
Эта функция возвращает символ ошибки из описания ошибки error.
- Функция: error-slot-value error pos
Эта функция возвращает значение поля с номером pos в описании ошибки error. Нумерация полей начинается с 1. Например, для ошибки типа
wrong-type-argumentфункция(error-slot-value error 2)возвращает объект, не прошедший проверку типа, а функция(error-slot-value error 1)— предикат, который не был выполнен.
- Функция: error-message-string error-descriptor
Эта функция возвращает строку сообщения об ошибке для заданного описания ошибки. Она полезна, если нужно обработать ошибку, выведя обычное сообщение об этой ошибке. См. Определение signal.
Вот пример использования condition-case для обработки ошибки, возникающей при делении на ноль. Обработчик отображает сообщение об ошибке (но без звукового сигнала), а затем возвращает очень большое число.
(defun safe-divide (dividend divisor)
(condition-case err
;; Protected form.
(/ dividend divisor)
;; The handler. (arith-error ; Condition. ;; Display the usual message for this error. (message "%s" (error-message-string err)) 1000000))) ⇒ safe-divide
(safe-divide 5 0)
-| Arithmetic error: (arith-error)
⇒ 1000000
В обработчике указано имя условия arith-error, поэтому он обрабатывает только ошибки деления на ноль. Ошибки других типов не обрабатываются (этой формой condition-case). Например:
(safe-divide nil 3)
error→ Wrong type argument: number-or-marker-p, nil
Вот форма condition-case, которая перехватывает ошибки всех типов, в том числе возникающие в error:
(setq baz 34)
⇒ 34
(condition-case err
(if (eq baz 35)
t
;; This is a call to the function error.
(error "Rats! The variable %s was %s, not 35" 'baz baz))
;; This is the handler; it is not a form.
(error (princ (format "The error was: %s" err))
2))
-| The error was: (error "Rats! The variable baz was 34, not 35")
⇒ 2
- Макрос: ignore-errors body…
-
Эта конструкция выполняет body, игнорируя все ошибки, возникающие во время выполнения. Если выполнение завершается без ошибок,
ignore-errorsвозвращает значение последней формы в body; в противном случае она возвращаетnil.Вот пример из начала этого подраздела, переписанный с использованием
ignore-errors:(ignore-errors (delete-file filename))
- Макрос: ignore-error condition body…
-
Этот макрос похож на
ignore-errors, но игнорирует только указанное условие ошибки.(ignore-error end-of-file (read ""))condition также может быть списком условий ошибки.
- Макрос: with-demoted-errors format body…
Этот макрос — более мягкий вариант
ignore-errors. Вместо полного подавления ошибок он преобразует их в сообщения. Для форматирования сообщения используется строка format. Строка format должна содержать одну последовательность «%»; например,"Error: %S". Используйтеwith-demoted-errorsдля кода, от которого не ожидается сигнализации об ошибках, но который должен оставаться надёжным, если ошибка всё же возникнет. Обратите внимание, что этот макрос используетcondition-case-unless-debug, а неcondition-case.
Иногда нужно перехватывать некоторые ошибки и записывать сведения об условиях, в которых они возникли, например полную трассировку стека или текущий буфер. К сожалению, такая информация недоступна в обработчиках формы condition-case, поскольку стек разворачивается до запуска обработчика, поэтому он выполняется в динамическом контексте формы condition-case, а не в контексте места, где была сигнализирована ошибка. В таких случаях можно использовать следующую форму:
- Макрос: handler-bind handlers body…
-
Эта специальная форма выполняет body и, если выполнение завершается без ошибки, возвращает полученное значение как значение формы
handler-bind. В этом случаеhandler-bindне оказывает никакого действия.handlers должен быть списком элементов вида
(conditions handler), где conditions — имя условия ошибки, которую нужно обработать, или список имён условий, а handler — форма, вычисление которой должно возвращать функцию. Как и в случае сcondition-case, имена условий являются символами.Перед выполнением body форма
handler-bindвычисляет все формы handler и устанавливает полученные обработчики, чтобы они действовали во время вычисления body. При сигнализации об ошибке Emacs ищет во всех активных формахcondition-caseиhandler-bindобработчик, в котором указано одно или несколько этих имён условий. Если самый внутренний подходящий обработчик установлен формойhandler-bind, функция handler вызывается с одним аргументом, содержащим описание ошибки.В отличие от поведения
condition-case, функция handler вызывается в динамическом контексте, где произошла ошибка. Это значит, что она выполняется без снятия привязок переменных и без выполнения очистки формunwind-protect, поэтому все эти динамические привязки остаются активными. Есть одно исключение: во время выполнения функции handler все обработчики ошибок между кодом, сигнализировавшим ошибку, и формойhandler-bindвременно приостанавливаются. Это означает, что при сигнализации об ошибке Emacs будет искать только активные формыcondition-caseиhandler-bind, находящиеся внутри функции handler или за пределами текущей формыhandler-bind. Обратите также внимание, что это не затрагивает переменные с лексической областью видимости (см. Лексическая область видимости), поскольку у них нет динамической области видимости.Как и любая обычная функция, handler может выполнить нелокальный выход, обычно с помощью
throw, либо нормально вернуть управление. Если handler возвращает управление обычным образом, значит обработчик отказался обрабатывать ошибку, и поиск обработчика ошибок продолжается с того места, где был прерван.Например, если нужно вести журнал всех ошибок, возникающих во время выполнения определённого фрагмента кода, вместе с буфером, который был текущим при сигнализации об ошибке, но при этом не изменять поведение этого кода, можно сделать так:
(handler-bind ((error (lambda (err) (push (cons err (current-buffer)) my-log-of-errors)))) body-forms...)В журнал будут записываться только ошибки, не перехваченные внутри body-forms…, то есть ошибки, которые «выходят» из body-forms…; это не помешает передаче этих ошибок окружающим обработчикам
condition-case(и обработчикамhandler-bind), поскольку приведённый выше обработчик возвращает управление обычным образом.С помощью
handler-bindможно также заменить одну ошибку другой, как в приведённом ниже коде, который преобразует все ошибки типаuser-error, возникающие во время выполнения body-forms…, в обычные ошибкиerror:(handler-bind ((user-error (lambda (err) (signal 'error (cdr err))))) body-forms...)Почти такого же результата можно добиться с помощью
condition-case:(condition-case err (progn body-forms...) (user-error (signal 'error (cdr err))))но есть отличие: когда мы повторно сигнализируем об ошибке в
handler-bind, динамическое окружение исходной ошибки всё ещё активно. Это означает, например, что если в этот момент войти в отладчик, он покажет полную трассировку стека, включая место, где была сигнализирована исходная ошибка:Debugger entered--Lisp error: (error "Oops") signal(error ("Oops")) #f(lambda (err) [t] (signal 'error (cdr err)))((user-error "Oops")) user-error("Oops") ... eval((handler-bind ((user-error (lambda (err) ...
Copyright © 1990-1996, 1998-2026 Free Software Foundation, Inc.
Licensed under the GNU GPL license.
https://www.gnu.org/software/emacs/manual/html_node/elisp/Handling-Errors.html