如何从Clojure错误中获得更好的反馈?
与我使用过的所有其他编程语言相比,我发现调试代码中的Clojure错误非常困难。我的主要编程语言是Java,我对Clojure非常陌生。我写Clojure的大部分时间都花在试图弄清楚“为什么我会犯这个错误?”我想改变这一点。我使用逆时针方向作为主要IDE。我还不知道如何使用Emacs 下面是一个例子:如何从Clojure错误中获得更好的反馈?,clojure,runtime-error,Clojure,Runtime Error,与我使用过的所有其他编程语言相比,我发现调试代码中的Clojure错误非常困难。我的主要编程语言是Java,我对Clojure非常陌生。我写Clojure的大部分时间都花在试图弄清楚“为什么我会犯这个错误?”我想改变这一点。我使用逆时针方向作为主要IDE。我还不知道如何使用Emacs 下面是一个例子: (ns cljsandbox.core) (def l [1 2 3 1]) (defn foo [l] (->> l (group-by identity)
(ns cljsandbox.core)
(def l [1 2 3 1])
(defn foo
[l]
(->> l
(group-by identity)
;vals ;commented out to show my intent
(map #(reduce + %))))
在这里,我错误地认为,groupby
返回一个列表列表,但它实际上返回了一个
的映射,或者用Java术语表达它。这将显示一条错误消息,其中显示:
ClassCastException clojure.lang.PersistentVector不能强制转换为java.lang.Number clojure.lang.Numbers.add(Numbers.java:126)
这不是很有帮助,因为没有堆栈跟踪。如果我输入(e)
,它会显示:
java.lang.ClassCastException: clojure.lang.PersistentVector cannot be cast to java.lang.Number
at clojure.lang.Numbers.add (Numbers.java:126)
clojure.core$_PLUS_.invoke (core.clj:944)
clojure.core.protocols/fn (protocols.clj:69)
clojure.core.protocols$fn__5979$G__5974__5992.invoke (protocols.clj:13)
clojure.core$reduce.invoke (core.clj:6175)
cljsandbox.core$foo$fn__1599.invoke (core.clj:10)
clojure.core$map$fn__4207.invoke (core.clj:2487)
clojure.lang.LazySeq.sval (LazySeq.java:42)
我不知道如何才能从这个错误消息理解“您以为您正在将列表列表传递到map
中,但实际上您正在传递一个map数据类型”。堆栈跟踪显示问题是在reduce
内部报告的,而不是在groupby
内部报告的,但在我看来,这不是我作为一个人犯的错误。这正是程序发现错误的地方
像这样的问题可能需要我15分钟以上才能解决。我怎样才能使这花费更少的时间
我知道期望一种动态语言捕捉这些错误是太过分了。但是,我觉得其他动态语言(如javascript)的错误消息更有帮助 我在这里感到非常绝望,因为我已经在clojure中编写代码1-2个月了,我觉得我应该更好地处理这些问题。我尝试在函数上使用
:pre
/:post
,但存在一些问题
:pre
/:post
的报道有点糟糕。它只打印出你测试的内容。因此,除非您投入大量精力,否则错误消息是没有帮助的李>
:pre
/:post
的代码是解释如何使用:pre
/:post
的文章defn
s中,这样我就可以把:pre
/:post
放进去,这真是一件痛苦的事(when (= next-url url)
(throw (IllegalStateException. (str "The next url and the current url are the same " url))))
(when-not (every? map? posts-list)
(throw (IllegalStateException. "parsed-html->posts must return a list of {:post post :source-url source-url}")))
这只修正了第一个要点
我也觉得
在这种情况下,发现问题的根源很容易:
->
管道做3。在REPL中尤其直截了当;一种方法是将输入和中间结果定义为临时变量,另一种方法是使用
*1
、*2
和*3
。(如果管道很长或者计算需要很多时间,我建议至少每隔几步执行一次临时的def
,否则*n
s就足够了。)
在其他情况下,您可能会做一些稍有不同的事情,但在任何情况下,将工作分解为可管理的块,以便在REPL中使用是关键。当然,熟悉Clojure的序列和集合库会大大加快这个过程;但是,在实际任务的小部分环境中使用它们是了解它们的更好方法之一。理解clojure异常的最好方法是了解clojure是使用Java类实现的,接口等。因此,每当您遇到此类异常时,请尝试将异常中提到的类/接口映射到clojure概念
例如:在当前的异常中,可以很容易地推断,
clojure.lang.PersistentVector
正在尝试在方法clojure.lang.Numbers.add
中键入cast tojava.lang.Number
。从这些信息中,您可以查看您的代码,直观地找出您在代码中使用add
的位置,即+
,然后通过以下事实诊断该问题:不知何故,this+将向量作为参数而不是数字 我发现clojure.tools.logging/spy宏对于调试非常有用。它打印出包装好的表达式及其值。如果您现在不想设置clojure.tools.logging(它需要正常的Java日志配置),您可以使用以下方法:
(defmacro spy
[& body]
`(let [x# ~@body]
(printf "=> %s = %s\n" (first '~body) x#)
x#))
*请记住,上面的代码不会打印出惰性seq的值,如果它还没有打印出来的话