Lisp - 调试
Common Lisp 中的调试
Section titled “Common Lisp 中的调试”Common Lisp 以其强大且交互式的调试能力而闻名。当 Lisp 程序遇到错误时,它通常不会直接崩溃,而是会进入一个交互式调试器 (debugger),允许开发者检查程序状态、修复问题并恢复执行。这促进了一种独特流畅且高效的开发工作流程。
条件系统与交互式调试器
Section titled “条件系统与交互式调试器”当错误发生时,Lisp 会发出 (signal) 一个条件 (condition)。在交互式环境(如 SLIME 或 REPL)中,此操作会调用调试器。调试器会提供丰富的错误上下文,包括一个栈回溯 (stack backtrace) 和一组可能的重启选项 (restarts)。
示例:运行时错误
Section titled “示例:运行时错误”考虑这段会引发除零错误的 Lisp 代码。
(defun divide (a b) (/ a b))
(defun calculate (x) (let ((y 0)) (divide x y)))
;; 此调用将触发调试器(calculate 10)模拟调试器交互
Section titled “模拟调试器交互”运行 (calculate 10) 会让你进入一个大致如下所示的会话:
Error: Division by zero in / of (10 0). [Condition of type DIVISION-BY-ZERO]
Restarts: 0: [RETRY] Retry the division operation. 1: [USE-VALUE] Use a different value for the result of (DIVIDE X Y). 2: [ABORT] Exit to the top-level REPL.
Backtrace: 0: (/) 1: (DIVIDE 10 0) | locals: A=10, B=0 2: (CALCULATE 10) | locals: X=10, Y=0
[1] (DIVIDE 10 0) >在此调试器提示符下,你可以:
- 检查状态 (Inspect State):栈回溯 (backtrace) 显示了调用栈 (call stack)。在现代环境中,你可以检查每个帧 (frame) 中局部变量 (
A、B、X、Y) 的值。 - 评估表达式 (Evaluate Expressions):你可以输入 Lisp 代码来进一步探查程序的状态。
- 调用重启选项 (Invoke Restarts):你可以选择一个重启选项来处理错误。例如,在提示符下输入
(use-value 42)会使(divide x y)调用返回42,程序将继续执行。
程序化追踪与单步执行
Section titled “程序化追踪与单步执行”使用 trace 追踪函数调用
Section titled “使用 trace 追踪函数调用”trace 宏允许你监控特定函数的进入和退出,并打印它们的参数和返回值。这对于理解程序流程而不必手动添加 print 语句来说是无价的。
;; 一个简单的递归阶乘函数(defun factorial (n) (if (zerop n) 1 (* n (factorial (1- n)))))
;; 开始追踪函数(trace factorial)
;; 调用函数(factorial 3)
;; 停止追踪(untrace factorial) 0: (FACTORIAL 3) 1: (FACTORIAL 2) 2: (FACTORIAL 1) 3: (FACTORIAL 0) 3: FACTORIAL returned 1 2: FACTORIAL returned 1 1: FACTORIAL returned 2 0: FACTORIAL returned 6使用 step 单步执行求值
Section titled “使用 step 单步执行求值”step 宏允许你一步一步地遍历 Lisp 表达式 (form) 的求值过程。它显示每个子表达式及其结果,提供了求值过程的细粒度视图。
;; 单步执行一个简单的算术表达式(step (+ (* 2 3) (1+ 4)))生产环境中的健壮错误处理
Section titled “生产环境中的健壮错误处理”虽然交互式调试器对于开发来说非常有用,但生产代码需要优雅地处理错误。handler-case 宏是实现这一功能的标准工具。它允许你执行代码并为可能发出的特定条件类型定义处理程序 (handler)。
(defun safe-divide (a b) "Divides a by b, returning NIL if division by zero occurs." (handler-case (/ a b) ;; 此子句仅在发出 DIVISION-BY-ZERO 条件时运行。 (division-by-zero (c) (format *error-output* "Error: Caught condition ~a~%" c) nil))) ; 错误时返回 nil
(safe-divide 10 2);=> 5
(safe-divide 10 0);=> Error: Caught condition #<DIVISION-BY-ZERO ...>;=> NIL