与我使用过的所有其他编程语言相比,我发现调试代码中的Clojure错误非常困难。我的主要编程语言是Java,我对Clojure非常陌生。我编写Clojure的大部分时间都花在试图弄清楚“为什么我会出现这个错误?”我想改变这一点。我使用CounterClockWise作为我的主要IDE。我不知道如何使用Emacs(还不知道吗?)。
这里有一个例子:
(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 + %))))
这里误以为group-by
返回的是列表列表,但实际返回的是的map
不能强制转换为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
传递列表列表,但你实际上是在传递地图数据类型”。堆栈跟踪显示问题是在duce
内部报告的,而不是在group-by
内部报告的,但IMO,这不是我作为一个人犯错误的地方。这只是程序发现错误的地方。
像这样的问题可能需要我15分钟才能解决。我怎样才能让这花更少的时间?
我知道期望动态语言捕获这些错误太过分了。但是,我觉得其他动态语言(如javascript)的错误消息更有帮助。
我在这里变得非常绝望,因为我已经在Clojure中编码了1-2个月,我觉得我应该更好地处理这些问题。我尝试使用: pre
/:post
在函数上,但有一些问题
: pre
/:post
的报告有点糟糕。它只从字面上打印出您测试的内容。因此,除非您投入大量精力,否则错误消息是没有帮助的。的代码:pre
/: post
是解释如何使用的文章:pre
/:post
。defn
中是非常痛苦的,这样我就可以将: 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中特别简单;一种方法是def
将输入和中间结果转换为临时Vars,另一种方法是使用*1
、*2
和*3
。(如果管道很长或计算需要大量时间,我建议至少每隔几步执行一次临时def
,否则*n
s可能就足够了。)
在其他情况下,你会做一些稍微不同的事情,但是无论如何,将工作分解成可管理的块以在REPL中使用是关键。当然,熟悉Clojure的序列和集合库会大大加快这个过程;但是在你正在处理的实际任务的小块背景下使用它们是了解它们的更好方法之一。
到目前为止,理解Clojure异常的最好方法(直到我们在Clojure中可能有Clojure)是理解Clojure是使用Java类、接口等实现的。所以每当你遇到这样的异常时,试着将异常中提到的类/接口映射到Clojure概念。
例如:在您当前的异常中,可以很容易地推断Clojure. lang.PerstientVector
正在尝试在Clojure.lang.Number.add
方法中键入cast到java.lang.Number
。从这些信息中,您可以查看您的代码并直观地找出您在代码中使用add
i.e的位置,然后通过以某种方式将向量作为参数而不是数字来诊断该问题。
我发现clojure.tools.日志/间谍宏对调试非常有用。它打印出包装好的表达式及其值。如果您现在不想设置clojure.tools日志记录(它需要正常的Java日志记录配置),您可以使用以下方法:
(defmacro spy
[& body]
`(let [x# ~@body]
(printf "=> %s = %s\n" (first '~body) x#)
x#))
*请记住,如果尚未实现,上面的代码不会打印出惰性seq的值。您可以检查惰性seq以强制实现它-不建议用于无限seq。
不幸的是,我还没有找到在线程宏中使用间谍宏的好方法,但它应该足以满足大多数其他情况。