提问者:小点点

如何从Clojure错误中获得更好的反馈?


与我使用过的所有其他编程语言相比,我发现调试代码中的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在函数上,但有一些问题

  1. : pre/:post的报告有点糟糕。它只从字面上打印出您测试的内容。因此,除非您投入大量精力,否则错误消息是没有帮助的。
  2. 这感觉不是很习惯。我见过的唯一使用的代码:pre/: post是解释如何使用的文章:pre/:post
  3. 将线程宏的步骤拉出到他们自己的defn中是非常痛苦的,这样我就可以将: pre/:post放在其中。
  4. 如果我虔诚地遵循这种做法,我想我的代码会变得和Java一样冗长。我会手工重新发明类型系统。

我已经到了用这样的安全检查来填充代码的地步:

(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}")))

这只修正了第一个要点。

我觉得要么

  1. 我有一个非常非常错误的开发过程,我不知道
  2. 有一些我不知道的调试工具/库,其他人都知道
  3. 其他人都遇到了这样的问题,这是Clojure肮脏的小秘密/其他人都习惯了动态语言,并希望通过与我相同的努力来解决错误
  4. CounterClockWise有些bug让我的生活变得更加艰难
  5. 我应该为我的Clojure代码编写比Java代码更多的单元测试。即使我正在编写一次性代码。

共3个答案

匿名用户

在这种特殊情况下,发现问题的根源很容易:

>

  • 我们有一个函数要应用于已知的项目向量。我们还期待一个特定的结果。

    应用该函数会导致问题。那么让我们看看函数内部;它恰好是一个-

    诊断问题最直接的方法是忽略管道的一些最后阶段,看看转换中的中间阶段是否如我们预期的那样。

    做3.在REPL中特别简单;一种方法是def将输入和中间结果转换为临时Vars,另一种方法是使用*1*2*3。(如果管道很长或计算需要大量时间,我建议至少每隔几步执行一次临时def,否则*ns可能就足够了。)

    在其他情况下,你会做一些稍微不同的事情,但是无论如何,将工作分解成可管理的块以在REPL中使用是关键。当然,熟悉Clojure的序列和集合库会大大加快这个过程;但是在你正在处理的实际任务的小块背景下使用它们是了解它们的更好方法之一。

  • 匿名用户

    到目前为止,理解Clojure异常的最好方法(直到我们在Clojure中可能有Clojure)是理解Clojure是使用Java类、接口等实现的。所以每当你遇到这样的异常时,试着将异常中提到的类/接口映射到Clojure概念。

    例如:在您当前的异常中,可以很容易地推断Clojure. lang.PerstientVector正在尝试在Clojure.lang.Number.add方法中键入cast到java.lang.Number。从这些信息中,您可以查看您的代码并直观地找出您在代码中使用addi.e的位置,然后通过以某种方式将向量作为参数而不是数字来诊断该问题。

    匿名用户

    我发现clojure.tools.日志/间谍宏对调试非常有用。它打印出包装好的表达式及其值。如果您现在不想设置clojure.tools日志记录(它需要正常的Java日志记录配置),您可以使用以下方法:

    (defmacro spy
      [& body]
      `(let [x# ~@body]
         (printf "=> %s = %s\n" (first '~body) x#)
         x#))
    

    *请记住,如果尚未实现,上面的代码不会打印出惰性seq的值。您可以检查惰性seq以强制实现它-不建议用于无限seq。

    不幸的是,我还没有找到在线程宏中使用间谍宏的好方法,但它应该足以满足大多数其他情况。