在Clojure中进行调试?

235

在使用repl时,调试Clojure代码的最佳方法是什么?


1
除了下面的答案,还可以在REPL指南中查看“调试工具和技术”:https://clojure.org/guides/repl/enhancing_your_repl_workflow#debugging-tools-and-techniques - Valentin Waeselynck
14个回答

163

还有一个dotrace工具,它允许您查看所选函数的输入和输出。

(use 'clojure.contrib.trace)
(defn fib[n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))
(dotrace [fib] (fib 3))

生成以下输出:

TRACE t4425: (fib 3)
TRACE t4426: |    (fib 2)
TRACE t4427: |    |    (fib 1)
TRACE t4427: |    |    => 1
TRACE t4428: |    |    (fib 0)
TRACE t4428: |    |    => 0
TRACE t4426: |    => 1
TRACE t4429: |    (fib 1)
TRACE t4429: |    => 1
TRACE t4425: => 2
2
在Clojure 1.4中,dotrace已经被移动:
你需要这个依赖项:
[org.clojure/tools.trace "0.7.9"]
(require 'clojure.tools.trace)

你需要在函数定义中添加 ^:dynamic。

(defn ^:dynamic fib[n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))

那么 Bob 再次成为你的叔叔:

(clojure.tools.trace/dotrace [fib] (fib 3))

TRACE t4328: (fib 3)
TRACE t4329: | (fib 2)
TRACE t4330: | | (fib 1)
TRACE t4330: | | => 1
TRACE t4331: | | (fib 0)
TRACE t4331: | | => 0
TRACE t4329: | => 1
TRACE t4332: | (fib 1)
TRACE t4332: | => 1
TRACE t4328: => 2

2
不错,但是你如何让Clojure找到'clojure.contrib.trace?我在我的类路径上有clojure-contrib jar,但REPL显示user=> (use 'closure.contrib.trace) java.io.FileNotFoundException: Could not locate closure/contrib/trace__init.class or closure/contrib/trace.clj on classpath: (NO_SOURCE_FILE:0) - LarsH
2
你可能把Clojure拼错成Closure了,或者这是注释中的打字错误吗?你能加载其他的clojure.contrib库吗? - John Lawrence Aspden
13
从1.3版本开始,这已经迁移到clojure.tools.trace(https://github.com/clojure/tools.trace)。 - George
4
如果您遇到以下错误信息: "IllegalStateException Can't dynamically bind non-dynamic var" 请参考这里: https://dev59.com/B2ox5IYBdhLWcg3w8IxJ - Cornelius
2
它在1.5版本中也能工作吗?我正在使用Clojure koans学习Clojure,但是还无法让dotrace正常工作。 - nha
很棒,可以突出核心库! - claj

105

我有一个小的调试宏,我觉得非常有用:

;;debugging parts of expressions
(defmacro dbg[x] `(let [x# ~x] (println "dbg:" '~x "=" x#) x#))
你可以将它插入到任何你想观察的地方,以查看正在进行的情况和时间:
;; Examples of dbg
(println (+ (* 2 3) (dbg (* 8 9))))
(println (dbg (println "yo")))
(defn factorial[n] (if (= n 0) 1 (* n (dbg (factorial (dec n))))))
(factorial 8)

(def integers (iterate inc 0))
(def squares  (map #(dbg(* % %))   integers))
(def cubes    (map #(dbg(* %1 %2)) integers squares))
(take 5 cubes)
(take 5 cubes)

9
更好的选择是:Spyscope - Zaz
@Zaz 我完全同意。Spyscope太棒了!甚至可能比调试器更好。特别是对于打字。 - J Atkin
1
非常有价值的答案,因为它不需要任何依赖。 - Juliano

68

Emacs的CIDER现已拥有一款源代码调试器,可以逐个表达式在Emacs缓冲区内调试,并注入新值。您可以在此处阅读相关信息。以下是演示截图:

CIDER debug


46

我最喜欢的方法是在代码中大量使用println... 通过使用#_读取宏(将阅读器读入以下形式,然后假装从未看到),轻松地打开和关闭它们。或者您可以使用一个宏,将其扩展为传入的主体或者nil,具体取决于某些特殊变量的值,比如说*debug*:

(defmacro debug-do [& body]
  (when *debug*
    `(do ~@body)))

如果将 (def *debug* false) 改为真值 true,它将扩展为包裹在 do 中的 body,否则将扩展为 nil。这个SO问题的被接受答案 在调试序列操作时非常有用。


此外,还有一些与 swank-clojure REPL 不兼容但是值得一提的工具:debug-repl。您可以在独立的REPL中使用它(例如使用 Leiningen (lein repl)很容易获得),如果从命令行启动程序,则会在终端中自动打开它自己的REPL。其思想是:您可以随时在任何地方加入 debug-repl 宏,并在程序执行到达该点时带出它自己的REPL,包括所有本地变量等。几个相关链接:Clojure debug-replClojure debug-repl tricks, how 'bout a debug-repl (on the Clojure Google group), debug-repl on Clojars


当使用 swank-clojure 与 Clojure 代码一起工作时,它可以很好地利用SLIME内置的调试器,注意到无关的堆栈跟踪部分被灰色处理,因此可以轻松找到实际的问题所在。要记住的一件事是,没有“名称标签”的匿名函数在堆栈跟踪中出现时几乎没有有用的信息附加在其上;当添加“名称标签”时,它会出现在堆栈跟踪中,一切又恢复正常了:

(fn [& args] ...)
vs.
(fn tag [& args] ...)

example stacktrace entries:
1: user$eval__3130$fn__3131.invoke(NO_SOURCE_FILE:1)
vs.                ^^
1: user$eval__3138$tag__3139.invoke(NO_SOURCE_FILE:1)
                   ^^^

5
实际上现在有一个适用于Swank的调试REPL版本:http://hugoduncan.org/post/2010/swank_clojure_gets_a_break_with_the_local_environment.xhtml(剧透:它非常棒) - user61051
1
没错,这里放一个链接很好,谢谢!同意太棒了。 :-) - Michał Marczyk
如果这是你的风格,你可能会喜欢在后续回复中提到的debux库。https://github.com/philoskim/debux - Mallory-Erik
@Mallory-Erik 谢谢,我会去看看! - Michał Marczyk

39

你还可以插入代码,使用Alex Osborne的debug-repl,将自己放入一个REPL中,其中包含所有本地绑定变量:

(defmacro local-bindings
  "Produces a map of the names of local bindings to their values."
  []
  (let [symbols (map key @clojure.lang.Compiler/LOCAL_ENV)]
    (zipmap (map (fn [sym] `(quote ~sym)) symbols) symbols)))

(declare *locals*)
(defn eval-with-locals
  "Evals a form with given locals. The locals should be a map of symbols to
values."
  [locals form]
  (binding [*locals* locals]
    (eval
     `(let ~(vec (mapcat #(list % `(*locals* '~%)) (keys locals)))
        ~form))))

(defmacro debug-repl
  "Starts a REPL with the local bindings available."
  []
  `(clojure.main/repl
    :prompt #(print "dr => ")
    :eval (partial eval-with-locals (local-bindings))))

然后,要使用它,请在您想要repl开始的任何地方插入它:

(defn my-function [a b c]
  (let [d (some-calc)]
    (debug-repl)))

我将这个代码放进了我的user.clj文件中,这样在所有的REPL会话中都可以使用它。


17

“使用repl调试Clojure代码的最佳方法”

略微有些意外,但是“使用repl本身”。

我已经写了一年余业余爱好者的Clojure,没有感觉到需要任何调试工具。如果您将函数保持小巧,并在REPL上用预期的输入运行每个函数并观察结果,那么应该能够对代码运行情况有一个相当清晰的认识。

我发现调试器在观察正在运行的应用程序的状态时最有用。Clojure使得以不可变数据结构(不改变状态)的方式编写变得容易(而且有趣!)。这大大减少了使用调试器的需求。一旦我知道所有组件的运作方式符合我的期望(特别关注事物的类型),那么大规模的行为很少会出现问题。


1
这基本上是正确的,但当你在多个函数之间进行递归时,就不那么容易了。 - John

9

9

对于IntelliJ,有一个名为Cursive的优秀Clojure插件。除其他外,它提供了一个REPL,您可以在调试模式下运行并像Java一样逐步执行Clojure代码。

不过,根据我的经验,仅在REPL中运行代码片段大多数情况下已足以进行调试。


我曾经成功地使用La Clojure,但现在似乎被Cursive所取代。 https://github.com/JetBrains/la-clojure/blob/master/README.md - leeor
但是如何使用 Leiningen 进行调试,它显示:Error running 'ring server': Trampoline must be enabled for debugging - Gank
这似乎是特定于 ringlein - 或许值得发布一个单独的问题? - dskrvk

6

截至2016年,您可以使用Debux,这是一个简单的调试库,适用于Clojure/Script,可与您的repl以及浏览器控制台一起使用。您可以在代码中使用dbg(debug)或clog(console.log)宏,并轻松观察打印到您的REPL和/或控制台的各个函数的结果等。

从该项目的Readme中:

Basic usage

This is a simple example. The macro dbg prints an original form and pretty-prints the evaluated value on the REPL window. Then it returns the value without interfering with the code execution.

If you wrap the code with dbg like this,

(* 2 (dbg (+ 10 20))) ; => 60

the following will be printed in the REPL window.

REPL output:

dbg: (+ 10 20) => 30

Nested dbg

The dbg macro can be nested.

(dbg (* 2 (dbg (+ 10 20)))) ; => 60

REPL output:

`dbg: (+ 10 20) => 30`  

dbg: (* 2 (dbg (+ 10 20))) => 60


5

Hugo Duncan和他的合作者们在ritz项目中继续做出惊人的工作。 Ritz-nrepl是一个带有调试功能的nREPL服务器。观看Hugo在Clojure/Conj 2012上的Debuggers in Clojure演讲,您可以在其中看到它的实际运行情况。由于视频中的一些幻灯片无法阅读,所以您可能需要从此处查看幻灯片。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接