Spec-Zone.ru › Elisp

Написание кода для обработки ошибок

Обычно при сигнализации об ошибке выполняющаяся команда завершается, и управление немедленно возвращается в цикл команд редактора 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API