mcpField
C
clj-debug

clj-debug

github.com/humorless开发4460 次安装22 周前更新
代码仓库

关于 clj-debug

clj-debug 是面向 Clojure 开发的 REPL 调试技能,指导开发者在 nrepl 中检查失败点的值并即时验证修复,而不是先修改源码添加日志。它还说明使用 Integrant 组件时,何时需要重置运行中的组件。

主要功能

  1. 01在 nrepl 中捕获并检查变量
  2. 02不改源码测试修复方案
  3. 03依据错误与堆栈定位问题
  4. 04处理 Integrant 组件重置
  5. 05提交前移除临时 def

适用场景

  1. 01定位函数参数或调用约定错误
  2. 02在写入源码前验证修复
  3. 03排查组件仍呈现旧行为的情况

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/humorless/clj-native-agent --skill clj-debug

安装后在 Claude Code 中输入/clj-debug确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

CHANGELOG.md
IMPROVEMENT_SUMMARY.md
evals/
evals.json
iteration-1-grading.md
SIDE_BY_SIDE_COMPARISON.md

详细介绍

为什么使用基于 REPL 的 Clojure 调试

添加日志并重新运行测试会造成缓慢的反馈循环。Clojure 的 REPL 让你可以在出错的位置直接检查值、即时测试修复,并在修改源文件前验证更正。这比 println 或正式调试器更快、更安全。

核心模式:先暂停,别记录日志

**危险信号:**你正准备添加 println、tap>、(log ...),或修改源代码来调试。

**改为:**在 nrepl 中使用内联 def。直接求值修改后的代码,无需更改文件;捕获并检查值,测试修复,然后放心提交。

内联 Def 模式:捕获并检查

函数出错时,在函数内部临时添加 (def variable-name variable-name),并在 nrepl 中求值修改后的函数。这样无需更改源文件即可捕获该值。

示例:发现调用约定错误

你的函数运行失败:

(defn foo [& [{:keys [a b c] :as m}]]
  (+ a b c))

(foo :a 1 :b 2 :c 3)
;; => java.lang.NullPointerException

在 nrepl 中,给函数添加内联 def,以捕获参数:

;; Evaluate this in nrepl (don't modify the source file):
(defn foo [& [{:keys [a b c] :as m}]]
  (def m m) ;; TODO: remove before commit
  (+ a b c))

;; Now call it:
(foo :a 1 :b 2 :c 3)
;; => java.lang.NullPointerException

;; Inspect what was captured:
m
;; => :a
;;    Aha! m is :a, not a map. The caller is passing wrong format.

现在你明白错误所在了:该函数需要的是映射 {:a 1 :b 2 :c 3},而不是关键字参数。

示例:在 nrepl 中测试修复后再提交

了解问题后,在 nrepl 中测试修正后的版本:

;; Test 1: Fix the calling convention
(foo {:a 1 :b 2 :c 3})
;; => 6   ✓ Works!

;; Test 2: Or fix the function to accept varargs correctly
(defn foo [& [{:keys [a b c] :as m}]]
  ;; No def here — we understand the bug now
  (+ a b c))

(foo {:a 1 :b 2 :c 3})
;; => 6   ✓ Verified!

;; Or change the function signature:
(defn foo [& args]
  (def args args) ;; Capture to verify varargs format
  (let [{:keys [a b c]} (first args)]
    (+ a b c)))

(foo {:a 1 :b 2 :c 3})
;; args => ({:a 1, :b 2, :c 3})   ✓ Correct format now

修复在 nrepl 中生效后,就可以放心更新源文件。提交前移除所有 (def ...) 行。

发现问题与修复工作流

以下是完整流程——使用 /brepl 逐步求值:

digraph debugging_workflow {
    rankdir=TB;
    node [shape=box, style=rounded];
    edge [fontsize=10];
    
    "Test Fails or\nError Occurs" -> "Read Error\nMessage &\nStack Trace";
    
    "Read Error\nMessage &\nStack Trace" -> "Identify Where\nto Inspect\n(Which function/\nwhich args?)";
    
    "Identify Where\nto Inspect\n(Which function/\nwhich args?)" -> "Write Function\nwith inline def\nin nrepl\n(def var var)";
    
    "Write Function\nwith inline def\nin nrepl\n(def var var)" -> "Evaluate Modified\nFunction in nrepl\n(defn foo ...)";
    
    "Evaluate Modified\nFunction in nrepl\n(defn foo ...)" -> "Call Function\nwith Test Args\n(foo ...)";
    
    "Call Function\nwith Test Args\n(foo ...)" -> "Inspect Captured\nValue at REPL\n(var / keys / type)";
    
    "Inspect Captured\nValue at REPL\n(var / keys / type)" -> "Form Hypothesis\n(Why is this\nwrong?)";
    
    "Form Hypothesis\n(Why is this\nwrong?)" -> "Test Corrected Code\nin nrepl\n(defn foo ...)";
    
    "Test Corrected Code\nin nrepl\n(defn foo ...)" -> "Verify Fix Works\n(Call & verify\nresults)";
    
    "Verify Fix Works\n(Call & verify\nresults)" -> "Update Source File\n(Remove inline def,\napply fix)";
    
    "Update Source File\n(Remove inline def,\napply fix)" -> "Run Test Suite\n(Confirm fix\nsolves problem)";
}

使用 Integrant 组件

如果代码使用了 Integrant 组件,请注意:**修改组件代码并在 nrepl 中求值后,正在运行的组件可能不会应用更改。**即使新代码已加载,你看到的也可能仍是旧行为。

何时重置组件

使用以下决策树:

digraph integrant_decision {
    rankdir=TB;
    node [shape=diamond, style=rounded];
    
    start [label="Modified code that\nthe component\nuses?", shape=diamond];
    no_reset [label="No reset needed\n(Only inspecting\nwith def or\ntesting pure\nfunctions)", shape=box, style=rounded];
    do_reset [label="Reset component\nto pick up\nchanges", shape=box, style=rounded];
    
    start -> no_reset [label="No"];
    start -> do_reset [label="Yes"];
}

重置方法:

;; After evaluating your code change in nrepl:
(require '[your.component :as comp] :reload)

;; Reset the component to pick up the new code:
(ig/halt! @system :component-key)
(alter-var-root #'@system (fn [s] (ig/init (:system/config @system) :component-key)))

;; Now test again — the component reflects your change

快速检查清单

  • 我是否阅读了错误消息和堆栈跟踪?
  • 我是否确定了要检查的函数和参数?
  • 我是否在 nrepl 中编写了带有 (def var var) 的函数?
  • 我是否求值了修改后的函数定义?
  • 我是否调用了函数以触发捕获?
  • 我是否检查了捕获的变量(键、类型、值)?
  • 我是否针对问题所在提出了假设?
  • 我是否先在 nrepl 中测试了修正后的代码?
  • 我是否在修改源文件前确认修复有效?
  • 我是否在提交前移除了 def 行?
  • 我是否考虑了组件生命周期(Integrant 状态)?

评论

支持 Markdown