29.3 Обзор концепций

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

29.3.1 Сигнализирование ошибок

Концептуально, сигнализирование ошибок в программе это указание того, что программа, не знает как продолжить выполнение и требует внешнего вмешательства. Как только сигнализирована ошибка, любые советы о продолжении должны прийти «извне».

Самый простой путь сигнализирования ошибки использовать функцию error с аргументами как для функции format, описывающие ошибку для пользовательского интерфейса. Если была вызвана error и активных обработчиков (описанных в разделах 29.3.2 и 29.3.3) не оказалось, то в управление вмешивается отладчик и данное сообщение выводится пользователю. Например:

Lisp> (defun factorial (x)
        (cond ((or (not (typep x ’integer)) (minusp x))
               (error "~S is not a valid argument to FACTORIAL."
                      x))
              ((zerop x) 1)
              (t (* x (factorial (- x 1))))))
  FACTORIAL
Lisp> (factorial 20)
  2432902008176640000
Lisp> (factorial -1)
Error: -1 is not a valid argument to FACTORIAL.
To continue, type :CONTINUE followed by an option number:
 1: Return to Lisp Toplevel.
Debug>

В общем, вызов error не может вернуть управление в то же место. Если не была проделана специальная работа по обработке для изменения этого поведения, то в работу вмешается отладчик и в нем не будет возможности просто продолжить работу.

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

(defun wargames:no-win-scenario ()
  (when (true) (error "Pushing the button would be stupid."))
  (push-the-button))

В этом сценарии, нет никаких шансов на то, что функция error вернёт управление, и кнопка будет нажата. _____________________________________

Примечание: It should be noted that the notion of “no chance” that the button will be pushed is relative only to the language model; it assumes that the language is accurately implemented. In practice, compilers have bugs, computers have glitches, and users have been known to interrupt at inopportune moments and use the debugger to return from arbitrary stack frames. Such violations of the language model are beyond the scope of the condition system but not necessarily beyond the scope of potential failures that the programmer should consider and defend against. The possibility of such unusual failures may of course also influence the design of code meant to handle less drastic situations, such as maintaining a database uncorrupted.—KMP and GLS__

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

Lisp> (defun factorial (x)
        (cond ((not (typep x ’integer))
               (error "~S is not a valid argument to FACTORIAL."
                      x))
              ((minusp x)
               (let ((x-magnitude (- x)))
                 (cerror "Compute -(~D!) instead."
                         "(-~D)! is not defined." x-magnitude)
                 (- (factorial x-magnitude))))
              ((zerop x) 1)
              (t (* x (factorial (- x 1))))))
  FACTORIAL
Lisp> (factorial -3)
Error: (-3)! is not defined.
To continue, type :CONTINUE followed by an option number:
 1: Compute -(3!) instead.
 2: Return to Lisp Toplevel.
Debug> :continue 1
  -6

29.3.2 Перехват ошибок

По умолчанию, вызов error приведёт к вмешательству отладчика. Но вы можете различными способами воспрепятствовать этому поведению. Самый простой (и самый грубый) инструмент для исключения вмешательства отладчика — использование ignore-errors. В обычной ситуации, формы в теле ignore-errors вычисляются последовательно и возвращает значение последней из них. Если сигнализируется ошибка error, ignore-errors немедленно возвращает два значение, nil и условие, которое было сигнализировано. Отладчик не вызывается и никакого сообщения об ошибке не выводится. Например:

Lisp> (setq filename "nosuchfile")
  "nosuchfile"
Lisp> (ignore-errors (open filename :direction :input))
  NIL and #<FILE-ERROR 3437523>

Второе возвращённое значение является объектом, отображающим тип ошибки. Он подробнее описан в разделе 29.3.4.

Однако, в большинстве случае ignore-errors не желательна, так как она обрабатывает слишком много типов ошибок. В отличие от утверждений некоторых людей, программа, которая не вызывает отладчик не обязательно лучше той, которая вызывает. Чрезмерное использование ignore-errors может удерживать программу от вмешательства отладчика, но надёжность программы от этого не увеличится, потому что программа может продолжить выполнение после ошибок, которые вами не учитывались. В целом, лучше попытаться обработать ряд ошибок, которые, как вы уверены, обязательно могут случиться. Таким образом, при появлении не ожидаемых ошибок, вы сможете о них узнать.

ignore-errors является полезным частным случаем от более общей функциональности, handler-case, которая позволяет программистам обрабатывать отдельные виды условий (включая не ошибочные условия) без указания того, что произойдёт при сигнализировании других видов условий. Например, эквивалентный код, как если бы использовалась ignore-errors, представлен в следующем примере:

Lisp> (setq filename "nosuchfile")
  "nosuchfile"
Lisp> (handler-case (open filename :direction :input)
        (error (condition)
          (values nil condition)))
  NIL and #<FILE-ERROR 3437525>

Однако, при использовании handler-case можно указать более специфичный тип условия, чем просто «error». Типы условий освещены будут освещены более подробно позже, но синтаксис примерно выглядит так:

Lisp> (makunbound ’filename)
  FILENAME
Lisp> (handler-case (open filename :direction :input)
        (file-error (condition)
          (values nil condition)))
Error: The variable FILENAME is unbound.
To continue, type :CONTINUE followed by an option number:
 1: Retry getting the value of FILENAME.
 2: Specify a value of FILENAME to use this time.
 3: Specify a value of FILENAME to store and use.
 4: Return to Lisp Toplevel.
Debug>

29.3.3 Обработка условий

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

Основная идея в обработке условия в том, что часть кода, называемого сигнальщик, распознаёт и объявление возникновение исключительной ситуации с помощью signal или другой функции на основе signal (такой как error).

Процесс сигнализирования включает поиск для и запуск обработчика - части кода, который будет пытаться соответственно справиться с ситуацией.

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

Так как лексическое окружение сигнальщика может быть не доступным для обработчика, для отображения состояния ситуации создаётся структура, называемая условие. Условие может быть создано как явным вызовом make-condition и передачей в функцию, например, signal, так и неявно в функции signal при использовании других аргументов.

При обработке ошибки, обработчик может использовать любой нелокальный выход (нелокальную передачу управления), такой как go на тег в tagbody, return из block, или throw в catch. Кроме того, для удобства в обработке исключений над этими примитивами созданы структурированные абстракции.

Обработчик может быть динамически доступным для программы если создать его с помощью handler-bind. Например, для создания обработчика для условия типа arithmetic-error, можно записать:

(handler-bind ((arithmetic-error handler))body)

Обработчик является функцией одного аргумента, а, именно, условия. Если во время выполнения форм body сигнализируется условие указанного типа (и внутри не было установленных обработчиков условия), будет вызван данный обработчик с данным условием, позволяя последнему передать управление куда-либо. Например, можно записать макрос, который выполняет тело, возвращая его значение(я) или два значения: nil и объект условия.

(defmacro without-arithmetic-errors (&body forms)
  (let ((tag (gensym)))
    ‘(block ,tag
       (handler-bind ((arithmetic-error
                         #’(lambda (c)     ;Argument c is a condition
                             (return-from ,tag (values nil c)))))
         ,@body))))

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

29.3.4 Объектно-ориентированные концепции обработки условий

Естественно, возможность обработчика качественно обработать исключительную ситуацию зависит от того, какой полноты информацию она предоставляет. Например, если все ошибки сигнализируется так:

(error "some format string")

тогда всё что получит обработчик - объект типа simple-error, в котором есть слот со строкой.

Если пользоваться только таким способом, то для различия ошибок придётся пользоваться функцией string-equal, и при каждом изменении строки об ошибке менять все сравнения в обработчиках, что не очень-то способствует самочувствию программиста. Этот феномен был самым большим фейлом в предыдущей системе обработки ошибок в Lisp’е. Принципиально важно отделить строки сообщений об ошибках (человеческий интерфейс) от объектов, которые формально представляют состояние ошибки (программный интерфейс). Таким образом мы имеем понятие типизированных условий и формальные операции над этими условиями, которые позволяют структурировано инспектировать эти условия.

Этот объектно-ориентированный подход в обработке условий имеет следующие важные преимущества по сравнению с текстовым подходом:

Некоторые типы условий определяются в этом документе, но их множество может расширяться с помощью define-condition. Common Lisp’овые типы условий фактически являются CLOS классами, и объекты условий являются простыми CLOS объектами. define-condition просто предоставляет интерфейс абстракции, который для определения условий чуть более удобен, чем defclass.

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

(defun divide (numerator denominator)
  (cond ((or (not (numberp numerator))
             (not (numberp denominator)))
         (error "(DIVIDE ’~S ’~S) - Bad arguments."
                numerator denominator))
        ((zerop denominator)
         (error ’division-by-zero
                :operator ’divide
                :operands (list numerator denominator)))
        (t ...)))

Следует отметить, что в первом выражении мы использовали error со строковым аргументом и во втором выражении мы указали конкретный тип условия, division-by-zero. В случае строкового аргумент, тип сигнализируемого условия будет simple-error.

Конкретный вид сигнализируемой ошибки может быть важен в зависимости от того где находятся активные обработчики. Например, simple-error наследуется от типа error, которая в свою очередь наследуется от типа condition. С другой стороны, division-by-zero наследуется от arithmetic-error, которая наследуется от error, которая наследуется от condition. Так что, если существует обработчик для arithmetic-error в момент когда сигнализируется division-by-zero, тогда этот обработчик будет вызван. Однако если в том же контексте будет вызвана simple-error, тип обработчика arithmetic-error вызываться не будет.

29.3.5 Перезапуски (рестарты)

Common Lisp’овая система условий создаёт чёткое разграничение между сигнализированием ошибки определённого типа и сообщением о том, как соответственно восстанавливать выполнение программы. В приведённом выше примере с divide просто сигнализирование ошибки не означает готовность сигнализатора к сотрудничеству в каких-либо корректирующих действиях. Например, следующий пример взаимодействия показывает, что для восстановление предлагается только «Вернуться на верхний уровень».

Lisp> (+ (divide 3 0) 7)
Error: Attempt to divide 3 by 0.
To continue, type :CONTINUE followed by an option number:
 1: Return to Lisp Toplevel.
Debug> :continue 1
Returned to Lisp Toplevel.
Lisp>

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

29.3.6 Анонимные перезапуски (рестарты)

Самый простой вид перезапуска включает в себя структурированную передачу управления с помощью макроса с именем restart-case. Форма restart-case позволяет выполнять кусок кода в контексте, где активны ноль или более перезапусков, и где, если один из этих перезапусков будет «вызван», управление будет передано на соответствующий пункт в restart-case формы. Например, мы могли бы переписать предыдущую функцию divide например, следующим образом.

(defun divide (numerator denominator)
  (loop
    (restart-case
        (return
          (cond ((or (not (numberp numerator))
                     (not (numberp denominator)))
                 (error "(DIVIDE ’~S ’~S) - Bad arguments."
                         numerator denominator))
                ((zerop denominator)
                 (error ’division-by-zero
                        :operator ’divide
                        :operands (list numerator denominator)))
                (t ...)))
      (nil (arg1 arg2)
          :report "Provide new arguments for use by DIVIDE."
          :interactive
            (lambda ()
               (list (prompt-for ’number "Numerator: ")
                     (prompt-for ’number "Denominator: ")))
        (setq numerator arg1 denominator arg2))
      (nil (result)
          :report "Provide a value to return from DIVIDE."
          :interactive
            (lambda () (list (prompt-for ’number "Result: ")))
        (return result)))))

__________________________________________________________________________

Примечание: The function prompt-for used in this chapter in a number of places is not a part of Common Lisp. It is used in the examples in this chapter only to keep the presentation simple. It is assumed to accept a type specifier and optionally a format string and associated arguments. It uses the format string and associated arguments as part of an interactive prompt, and uses read to read a Lisp object; however, only an object of the type indicated by the type specifier is accepted.

The question of whether or not prompt-for (or something like it) would be a useful addition to Common Lisp is under consideration by X3J13, but as of January 1989 no action has been taken. In spite of its use in a number of examples, nothing in the Common Lisp Condition System depends on this function.

В примере, nil, указанный в начале каждого выражения, означает, что это «анонимный» перезапуск. Анонимные перезапуски обычно вызываются только из отладчика. Мы покажем позже, что возможно иметь «именованный перезапуск», который может быть вызван из кода, без необходимости пользовательского вмешательства.

Если аргументы для анонимного перезапуска являются обязательными, тогда должна быть предоставлена специальная информация о том, что отладчик должен использовать в качестве аргументов. Здесь для указания этой информации используется ключевой символ :interactive.

Ключевое слово :report представляет информацию, для использования когда предоставляет параметр перезапуска пользователю (в отладчике, например).

Вот простое взаимодействие, которое использует перезапуски предоставленные изменённой версией divide:

Lisp> (+ (divide 3 0) 7)
Error: Attempt to divide 3 by 0.
To continue, type :CONTINUE followed by an option number:
 1: Provide new arguments for use by the DIVIDE function.
 2: Provide a value to return from the DIVIDE function.
 3: Return to Lisp Toplevel.
Debug> :continue 1
1
Numerator: 4
Denominator: 2
  9

29.3.7 Именованные перезапуски (рестарты)

В дополнение к анонимным перезапускам, можно задавать перезапуски с именами, которые могут быть вызваны из кода, используя это имя. В качестве простейшего примера, для сложения 3 и 1 и возвращения 4, можно записать:

(restart-case (invoke-restart ’foo 3)
  (foo (x) (+ x 1)))

Этот пример концептуально аналогичен следующему:

(+ (catch ’something (throw ’something 3)) 1)

Для более реалистичного примера, код функции symbol-value может сигнализировать ошибку несвязанной переменной так:

(restart-case (error "The variable ~S is unbound." variable)
  (continue ()
      :report
        (lambda (s)     ;Аргумент s — поток
          (format s "Retry getting the value of ~S." variable))
    (symbol-value variable))
  (use-value (value)
      :report
        (lambda (s)     ;Аргумент s — поток
          (format s "Specify a value of ~S to use this time."
                  variable))
    value)
  (store-value (value)
      :report
        (lambda (s)     ;Аргумент s — поток
          (format s "Specify a value of ~S to store and use."
                  variable))
    (setf (symbol-value variable) value)
    value))

Если это будет частью реализации symbol-value, тогда пользователи смогут писать разные автоматические обработчики ошибок о несвязанных переменных. Например, для вычисления несвязанной переменной самой в себя, можно записать:

(handler-bind ((unbound-variable
                 #’(lambda (c)     ;Аргумент c — условие
                     (when (find-restart ’use-value)
                       (invoke-restart ’use-value
                                       (cell-error-name c))))))
  body)

29.3.8 Функции перезапусков (рестартов)

Для частого использования перезапусков, удобно определить программный интерфейс, который будет скрывать использование invoke-restart. Такой программный интерфейс для перезапусков называется restart functions.

Обычно функция имеет такое же имя, что и перезапуск. Предопределённые функции abort, continue, muffle-warning, store-value и use-value являются функциями перезапусков. Предыдущий пример может быть записан с использованием use-value:

(handler-bind ((unbound-variable
                   #’(lambda (c)     ;Argument c is a condition
                       (use-value (cell-error-name c)))))
  body)

29.3.9 Сравнение перезапусков и catch/throw

Одна важная возможность в том, что предоставляет restart-case (или restart-bind), и не предоставляет catch, это получение информации о доступных точках для передачи управления без самой попытки передачи. Можно например написать так:

(ignore-errors (throw ...))

что является обеднённой реализацией

(when (find-restart ’something)
  (invoke-restart ’something))

однако с помощью ignore-errors и throw невозможно реализовать следующий код:

(when (and (find-restart ’something)
           (find-restart ’something-else))
  (invoke-restart ’something))

или даже такой:

(when (and (find-restart ’something)
           (yes-or-no-p "Do something? "))
  (invoke-restart ’something))

так как уровень инспекции кода в простом коде

(ignore-errors (throw ...))

слишком примитивный — после получения информации о переходе, немедленно производится и сам переход.

Многие программисты используют ранее развивающуюся стратегию, как например:

(defvar *foo-tag-is-available* nil)

(defun fn-1 ()
  (catch ’foo
    (let ((*foo-tag-is-available* t))
      ... (fn-2) ...)))

(defun fn-2 ()
  ...
  (if *foo-tag-is-available* (throw ’foo t))
  ...)

Функциональность от restart-case и find-restart предназначена для стандартизированного протокол для такого типа информации, при коммуникации между написанными независимо друг от друга программами. Поэтому модульность и возможность отладки для этих программ не нарушаются.

Другое отличие между функциональностями перезапусков и catch/throw заключается в том, что catch с некоторым тегом полностью скрывают любые внешние формы catch с одноимённым тегом. Из-за наличия compute-restarts, есть возможность для просмотра скрытых перезапусков, которые могут быть полезны в некоторых ситуациях (в частности в интерактивном отладчике).

29.3.10 Обобщённые перезапуски

restart-case является механизмом, который поддерживает только императивную передачу управления в связанные перезапуски. restart-case построена на низкоуровневом механизме с названием restart-bind, который не принуждает к передаче управления.

restart-bind для restart-case, как handler-bind для handler-case. Синтаксис выглядит так:

(restart-bind ((name function . options)) . body)

Формы body выполняются в динамическом окружении, внутри которого будет вызвана функция function, где бы ни была вызвана форма (invoke-restart ’name). Аргументы options перечисляются с помощью ключевых символом и используются для передачи информации, такой, например, какую предоставляет ключевой символ :report в restart-case.

restart-case раскрывается в вызов restart-bind, где функция просто делает безусловную передачу выполнения в конкретный код, указывая при этом в структурированном виде «аргументную» информацию.

Можно также записывать перезапуски, которые не передают управления. Такие перезапуски могут быть полезны для реализации различных специальных команд для отладчика, которые представляют интерес только в конкретной ситуации. Например, можно представить ситуацию, в которой для файлов закончилось место и для продолжения операции было освобождено место в директории dir:

(restart-bind ((nil #’(lambda () (expunge-directory dir))
                    :report-function
                      #’(lambda (stream)
                          (format stream "Expunge ~A."
                                  (directory-namestring dir)))))
  (cerror "Try this file operation again."
          ’directory-full :directory dir))

В этом случае, должен вмешаться отладчик и пользователь сначала освободит место (это не приводит к передаче управления из отладчика куда-либо) и затем повторит операцию:

Lisp> (open "FOO" :direction :output)
Error: The directory PS:<JDOE> is full.
To continue, type :CONTINUE followed by an option number:
 1: Try this file operation again.
 2: Expunge PS:<JDOE>.
 3: Return to Lisp Toplevel.
Debug> :continue 2
Expunging PS:<JDOE> ... 3 records freed.
Debug> :continue 1
  #<OUTPUT-STREAM "PS:<JDOE>FOO.LSP" 2323473>

29.3.11 Интерактивная обработка условий

Когда программа не знает как продолжить выполнение, и нет активных обработчиков, которые могли бы дать совет по этому поводу, тогда может придти «интерактивный обработчик условий» или, другими словами, «отладчик». Это неявно происходит при использовании таких функций, как error и cerror, или явно с помощью функции invoke-debugger.

Интерактивный обработчик условий никогда не возвращает управления напрямую. Он возвращает управление с помощью нелокальной структурированной передачи управления в специально определённые точки перезапуска, которые могли быть установлены как системой, так и пользовательским кодом. Механизмы, которые поддерживают создание таких структурированных точек перезапуска для портируемого кода изложены в разделах с 29.3.5 по 29.3.10.

Фактически, реализации могут также содержать расширенную функциональность для отладки, которая позволяет возврат из произвольного фрейма стека. Не смотря на то, такие команды часто полезны на практике, их действия зависят от реализации, потому что они нарушают абстрагированность Common Lisp’овой программы. Результаты использования таких команд настоящим стандартом не определены.

29.3.12 Важные условия

Макрос ignore-errors будет ловить условия типа error. Однако существуют также условия других типов.

Некоторые условия не считаются ошибками, но всё равно являются важными. Такие условия называются важными условиями, и для их представления используется тип serious-condition. Условия, которые, например, могут сигнализироваться для «переполнения стека» или «исчерпанности хранилища», относятся к данной категории.

Тип error является подтипом serious-condition, и технически было бы правильным использовать термин «важное условие» для ссылки на все важные условия, будь то ошибки или нет. Однако, обычно мы используется термин «важное условие» для ссылки на вещи типа serious-condition, но не типа error.

Смысл в разницу между ошибками и другими серьёзными условиями в том, что некоторые условия возникают по причинам, которые выходят за рамки Common Lisp’а. Например, мы знаем, что стек обычно используется для реализации вызова функций, и мы знаем, что стеки имеют конечный размер и могут переполнятся. Так как размер стека может меняться от реализации к реализации, от сессии к сессии, или от вызова к вызову функции, то выражение (ignore-errors (+ a b)), которое будет возвращать разные значения, в определённый момент, например, из-за переполнения стека. Поэтому, ignore-errors ловит условия только типа error, но не все serious-condition. Для обработки других условий может быть использована низкоуровневая функциональность (такая как handler-bind или handler-case).

По соглашению, для сигнализирования условий типа serious-condition (включая error) функция error предпочтительнее чем signal. It is the use of the function error, and not the type of the condition being signaled, that actually causes the debugger to be entered.

29.3.13 Неважные условия

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

Например, реализация может выбрать сигнализирование неважного (и зависимого от реализации) условия, называемого end-of-line, когда вывод достигает последнего символа в строке. В такой реализации, сигнализирование такого условия позволяет другим программа вмешаться, выполняя вывод с переводом строки.

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

29.3.14 Типы условий

Некоторые типы условий определены в самой системе. Все типы условий являются подтипами condition. То есть, (typep x ’condition) является истиной тогда и только тогда, когда значение x является условием.

Типы условий допускают множественное наследование. FIXME

29.3.15 Сигнализирование условий

При сигнализировании условия система пытается найти наиболее соответствующий обработчик и вызвать его.

Обработчики устанавливаются динамически с помощью handler-bind или абстракций над handler-bind.

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

Если обработчик найден не быть, или если все обработчики, которые были найдены отклонили предложение, signal возвращает nil.

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

29.3.16 Пересигнализирование условий

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

Например, системе необходимо выделить память под объекты типа storage-condition, так, чтобы они могли быть сигнализированы в нужное время без попыток выделения памяти.

29.3.17 Обработчики условий

Обработчик является функцией одного аргумента, а именно, условия для обработки. Обработчик может проинспектировать объект для того, чтобы быть уверенным в «интересности» оного.

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

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

Фактически последние два действия (сигнализирование другого условия или вызов отладчика) просто перекладывают решение на кого-либо другого. В конечном счёте, все что обработчики могут сделать это обработать или отклонить.

29.3.18 Вывод условий

Когда *print-escape* является nil (например, когда используются функция princ или директива ~A для format), , будет вызван метод для вывода условия. Это будет быть сделано автоматически в функциях, таких как invoke-debugger, break и warn, но иногда бывает полезно иметь возможность повлиять на вывод условия. Например,

(let ((form ’(open "nosuchfile")))
  (handler-case (eval form)
    (serious-condition (c)
      (format t "~&Вычисление ~S неуспешно:~%~A" form c))))

может вывести что-то вроде

Вычисление (OPEN "nosuchfile") неуспешно:
Файл "nosuchfile" не был найден.

Некоторые предложения о форме текст, введённый в докладе методы:

Когда *print-escape* не равно nil, объект должен быть выведен в некотором полезном виде в соотвествие со стилем реализации. Не нужно чтобы данный вывод был считываемым с помощью read. Что-нибудь вроде #<ARITHMETIC-ERROR 1734> достаточно.

Указание (:report fn) для define-condition при определении типа условия C эквивалентно отдельному определению метода:

(defmethod print-object ((x C) stream)
  (if *print-escape*
      (call-next-method)
      (funcall #’fn x stream)))

Следует отметить, что метод использует fn для вывода условия только тогда, когда *print-escape* имеет значение nil.