handler-case — 조건(condition)에 따라 처리하기

handler-case — 조건(condition)에 따라 처리하기

언제 어떤 오류가 날지 모르는 코드를 짤 때, 예외가 나면 그 상황에 맞는 응답을 하고 싶어요. 파일이 없으면 다른 경로를 쓰고, 수치 오류면 기본값을 돌려주는 식이죠. Common Lisp에서 이런 처리를 하는 대표적인 매크로가 handler-case예요. 조건 타입별로 처리 절을 나열해, 오류가 발생하면 맞는 절로 제어가 넘어가요.

출처: CLHS 공식 문서 - HANDLER-CASE

본문

handler-case는 여러 핸들러가 활성화된 동적 환경에서 expression을 실행해요. 각 error-clause는 지정한 타입과 맞는 조건이 생겼을 때 어떻게 처리할지를 정해요.

handler-case expression [[{error-clause}* | no-error-clause]]
=> result*

error-clause ::= (typespec ([var]) declaration* form*)
no-error-clause ::= (:no-error lambda-list declaration* form*)

expression 실행 중에 적절한 error-clause가 있고(즉 (typep condition 'typespec)이 참이고) 그 타입을 처리하는 중간 핸들러가 없다면, 제어가 해당 절의 본문으로 넘어가요. 이때 동적 상태가 적절히 풀려서 expression 주변의 핸들러는 더 이상 활성 상태가 아니고, var에 신호된 조건이 묶여요.

절이 여러 개면 절들은 위에서 아래로 순서대로 검색돼요. 그래서 타입이 겹치는 경우에는 앞에 있는 절이 선택돼요. var가 필요 없다면 아예 생략해서 (typespec () form)처럼 쓸 수도 있어요. 선택된 절에 폼이 하나도 없으면 그 절, 나아가 handler-case 전체는 nil을 돌려줘요.

(defun assess-condition (condition)
  (handler-case (signal condition)
    (warning () "Lots of smoke, but no fire.")
    ((or arithmetic-error control-error cell-error stream-error)
     (condition)
     (format nil "~S looks especially bad." condition))
    (serious-condition (condition)
      (format nil "~S looks serious." condition))
    (condition () "Hardly worth mentioning.")))
=>  ASSESS-CONDITION

(assess-condition (make-condition 'stream-error :stream *terminal-io*))
=>  "#<STREAM-ERROR 12352256> looks especially bad."

여러 절이 있을 때 절들은 병렬로 노출돼요. 즉 한 절이 선택되면 다른 절의 핸들러는 그 시점에 보이지 않게 되는 거죠.

expression이 정상적으로 반환되는데 :no-error 절이 있다면, 반환된 값들을 그 절의 lambda-list가 받아 폼들을 실행해요. 이때도 expression 주변에 걸어두었던 핸들러는 이미 비활성화된 상태예요.

(handler-case form
  (type1 (var1) . body1)
  (type2 (var2) . body2)
  ...
  (:no-error (varN-1 varN-2 ...) . bodyN))

함께 보기

handler-bind, ignore-errors