Debugging in Clojure?
使用repl时,调试Clojure代码的最佳方法是什么?
还有dotrace,它使您可以查看选定函数的输入和输出。
1 2 3 | (use 'clojure.contrib.trace) (defn fib[n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) (dotrace [fib] (fib 3)) |
产生输出:
1 2 3 4 5 6 7 8 9 10 11 | 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中,
您需要依赖项:
1 2 | [org.clojure/tools.trace"0.7.9"] (require 'clojure.tools.trace) |
并且您需要在函数定义中添加^:dynamic
1 | (defn ^:dynamic fib[n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) |
然后鲍勃再次成为你的叔叔:
1 2 3 4 5 6 7 8 9 10 11 12 | (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 |
我有一个小的调试宏,我发现它非常有用:
1 2 | ;;debugging parts of expressions (defmacro dbg[x] `(let [x# ~x] (println"dbg:" '~x"=" x#) x#)) |
您可以将其插入任何想看发生什么情况的时间以及何时:
1 2 3 4 5 6 7 8 9 10 11 | ;; 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) |
Emacs的CIDER提供了一个源调试器,您可以在Emacs缓冲区内逐个表达式地表达表达式,甚至可以注入新值。你可以在这里读到所有和它有关的。演示屏幕截图:
我最喜欢的方法是在代码中随意散布
1 2 3 | (defmacro debug-do [& body] (when *debug* `(do ~@body))) |
带有
对此问题的公认答案:用于进度报告的惯用Clojure?在调试序列操作时非常有用。
然后有一些当前与swank-clojure的REPL不兼容的东西,但太好了,更不用说了:
swank-clojure可以很好地使SL??IME的内置调试器在使用Clojure代码时发挥作用-请注意,stacktrace的无关位是如何变灰的,因此很容易在被调试的代码中找到实际的问题。要记住的一件事是,没有"名称标签"的匿名函数出现在堆栈跟踪中,基本上没有附加有用的信息;当添加"名称标签"时,它确实出现在堆栈跟踪中,并且一切都很好:
1 2 3 4 5 6 7 8 9 | (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) ^^^ |
您还可以使用Alex Osborne的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | (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开始的任何地方:
1 2 3 | (defn my-function [a b c] (let [d (some-calc)] (debug-repl))) |
我将此粘贴在user.clj中,以便在所有REPL会话中都可用。
"使用REPL时调试Clojure代码的最佳方法"
略微偏左,但"使用REPL本身"。
我已经写了业余爱好者Clojure一年多了,对任何调试工具的需求都没有。如果您使函数保持较小,并在REPL上使用预期的输入运行每个函数并观察结果,那么应该可以很清楚地了解代码的行为方式。
我发现调试器对于观察正在运行的应用程序中的STATE最有用。 Clojure使使用具有不变数据结构(无变化状态)的功能样式编写起来容易(有趣)!这大大减少了对调试器的需求。一旦我知道所有组件的行为都符合我的预期(要特别注意事物的类型),那么大规模行为就很少有问题了。
对于IntelliJ,有一个出色的Clojure插件称为Cursive。除其他外,它提供了一个REPL,您可以在调试模式下运行该REPL并逐步执行Clojure代码,就像您进行例如Java。
尽管我认为彼得·韦斯特马科特(Peter Westmacott)的回答是第二位的,但根据我的经验,在大多数情况下,仅在REPL中运行我的代码片段是一种足够的调试形式。
如果您使用emacs / slime / swank,请在REPL上尝试以下操作:
1 2 3 4 5 6 | (defn factorial [n] (cond (< n 2) n (= n 23) (swank.core/break) :else (* n (factorial (dec n))))) (factorial 30) |
它不会像在LISP下那样为您提供完整的堆栈跟踪信息,但是它对戳戳非常有用
周围。
这是以下方面的优良工作:
http://hugoduncan.org/post/2010/swank_clojure_gets_a_break_with_the_local_environment.xhtml
正如上面评论中提到的那样。
从2016年开始,您可以使用Debux,这是Clojure / Script的简单调试库,可与您的repl以及浏览器的控制台配合使用。您可以在代码中添加
从项目的自述文件中:
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:
1 `dbg: (+ 10 20) => 30`
dbg: (* 2 (dbg (+ 10 20))) => 60
雨果·邓肯(Hugo Duncan)和合作者继续在Ritz项目中做出色的工作。 Ritz-nrepl是具有调试功能的nREPL服务器。在Clojure / Conj 2012上观看Hugo的Clojure中的Debuggers演讲,观看它的运行,在视频中一些幻灯片不可读,因此您可能希望从此处查看幻灯片。
使用实现自定义阅读器宏的spyscope,以便您的调试代码也是生产代码
https://github.com/dgrnbrg/spyscope
这是一个调试复杂的
1 2 3 4 5 6 7 8 | (defmacro def+ "def with binding (def+ [{:keys [a b d]} {:a 1 :b 2 :d 3}])" [bindings] (let [let-expr (macroexpand `(let ~bindings)) vars (filter #(not (.contains (str %)"__")) (map first (partition 2 (second let-expr)))) def-vars (map (fn [v] `(def ~v ~v)) vars)] (concat let-expr def-vars))) |
...以及一篇说明其用途的文章。
来自Java并熟悉Eclipse,我喜欢逆时针(Clojure开发的Eclipse插件)所提供的功能:http://doc.ccw-ide.org/documentation.html#_debug_clojure_code
def-let的函数版本,它将let转换为一系列defs。功劳归功于这里
1 2 3 4 5 6 7 8 9 10 11 | (defn def-let [aVec] (if-not (even? (count aVec)) aVec (let [aKey (atom"") counter (atom 0)] (doseq [item aVec] (if (even? @counter) (reset! aKey item) (intern *ns* (symbol @aKey) (eval item))) ; (prn item) (swap! counter inc))))) |
用法:需要用引号将内容引号,例如
1 | (def-let '[a 1 b 2 c (atom 0)]) |