简体   繁体   English

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

[英]How do I get better feedback from Clojure errors?

I find it very difficult to debug the Clojure errors I have in my code compared to all the other programming languages I've used. 我发现在我的代码中调试Clojure错误与我使用的所有其他编程语言相比非常困难。 My primary programming language is Java and I'm very new to Clojure. 我的主要编程语言是Java,我对Clojure很新。 The majority of my time writing Clojure is spent trying to figure out, "Why am I getting this error?" 我写Clojure的大部分时间都花在试图弄清楚“为什么我得到这个错误?” and I'd like to change that. 我想改变这一点。 I'm using CounterClockWise as my primary IDE. 我正在使用CounterClockWise作为我的主要IDE。 I don't know how to use Emacs (yet?). 我不知道如何使用Emacs(但是?)。

Here's an example: 这是一个例子:

(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 + %))))

Here, I mistakenly thought that group-by returns a list of lists, but it actually returns a map of <key, list<value>> or however you'd phrase it in Java terms. 在这里,我错误地认为group-by返回一个列表列表,但它实际上返回了<key, list<value>>的映射,或者你用Java术语表示它。 This gives an error message that says: 这会显示一条错误消息:

ClassCastException clojure.lang.PersistentVector cannot be cast to java.lang.Number clojure.lang.Numbers.add (Numbers.java:126) ClassCastException clojure.lang.PersistentVector无法强制转换为java.lang.Number clojure.lang.Numbers.add(Numbers.java:126)

This isn't very helpful because there isn't a stack trace. 这不是很有用,因为没有堆栈跟踪。 If I type (e) it says: 如果我输入(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)

I have no idea how I can go from this error message to understanding, "You thought you were passing in a list of lists into map but you were really passing in a map datatype". 我不知道如何从这个错误消息中理解,“你以为你将列表列表传入map但你真的传递了一个map数据类型”。 The stack trace shows the problem was reported inside of reduce , not inside of group-by , but IMO, that is not where I as a human made my mistake. 堆栈跟踪显示问题是在reduce内部报告的,而不是在group-by内部,但IMO,这不是我作为一个人的地方犯了我的错误。 That's just where the program discovered a mistake had been made. 这就是程序发现错误的地方。

Issues like these can take me 15+ minutes to resolve. 这些问题可能需要15分钟才能解决。 How can I make this take less time? 我怎样才能减少时间?


I know it's too much to expect a dynamic language to catch these errors. 我知道期望动态语言能够捕获这些错误太过分了。 But, I feel like the error messages of other dynamic languages like javascript are much more helpful. 但是,我觉得像javascript这样的其他动态语言的错误消息更有帮助。

I'm getting pretty desperate here, because I've been coding in clojure for 1-2 months now and I feel like I should have a better handle on figuring out these problems. 我在这里非常绝望,因为我现在已经在clojure编写1-2个月了,我觉得我应该更好地解决这些问题。 I tried using :pre / :post on functions but that has some problems 我尝试使用:pre / :post on functions但是有一些问题

  1. The reporting on :pre / :post kind of sucks. 报告:pre / :post有点糟糕。 It only prints out literally what you test. 它只打印出你测试的字面。 So unless you put a lot of effort into it, the error message isn't helpful. 因此,除非您付出很多努力,否则错误消息无济于事。
  2. This doesn't feel very idiomatic. 这感觉不是很惯用。 The only code I've seen that uses :pre / :post are articles that explain how to use :pre / :post . 我见过的唯一代码使用:pre / :post是解释如何使用的文章:pre / :post
  3. It's a real pain to pull out the steps of a threading macro into their own defn s so that I can put the :pre / :post in them. 将线程宏的步骤拉出到自己的defn是非常defn的,这样我就可以将:pre / :post放在其中。
  4. If I followed this practice religiously, I think my code could become as verbose as Java. 如果我虔诚地遵循这种做法,我认为我的代码可能会变得像Java一样冗长。 I'd be reinventing the type system by hand. 我会手工重塑类型系统。

I've gotten to the point where I pepper my code with safety checks like this: 我已经到了这样的地方,我用这样的安全检查来代码:

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

Which only fixes that first bullet point. 其中只修复了第一个要点。

I feel like either 我也想要

  1. I've got a development process that's very, very wrong and I don't know it 我有一个非常非常错误的开发过程,我不知道
  2. There's some debugging tool/library out there that I don't know about that everyone else does 那里有一些调试工具/库,我不知道其他人都在做什么
  3. Everyone else is having problems like this and it's Clojure's dirty little secret / Everyone else is used to dynamic languages and expects to go through the same effort I am going through to resolve errors 其他人都有这样的问题,这是Clojure肮脏的小秘密/其他所有人都习惯使用动态语言,并期望我经历同样的努力来解决错误
  4. CounterClockWise has some bug that's making my life way harder than it needs to be CounterClockWise有一些错误,使我的生活变得比它需要的更难
  5. I'm supposed to be writing a lot more unit tests for my Clojure code than I do for my Java code. 我应该为我的Clojure代码编写比我的Java代码更多的单元测试。 Even if I'm writing throwaway code. 即使我正在写一次性代码。

In this particular instance, discovering the source of the problem is easy: 在这个特定的例子中,发现问题的根源很容易:

  1. We've got a function to be applied to a known vector of items. 我们有一个函数可以应用于已知的项目向量。 We're also expecting a particular result. 我们也期待一个特定的结果。

  2. Applying the function results in a problem. 应用该功能会导致问题。 Let's peek inside the function then; 让我们看看函数内部; it happens to be a ->> pipeline. 它碰巧是->>管道。

  3. The most straightforward way to diagnose the problem is to leave off some of the final stages of the pipeline to see if the intermediate stages in the transformation are as we expect. 诊断问题最直接的方法是放弃管道的一些最后阶段,看看转换中的中间阶段是否符合我们的预期。

Doing 3. is particularly straightforward at the REPL; 在REPL中做第3步特别简单; one approach is to def the input and the intermediate results to temporary Vars, another is to use *1 , *2 and *3 . 一种方法是将def输入和中间结果来临时瓦尔,另一个是使用*1*2*3 (If the pipeline is long or the computations take a lot of time, I'd recommend doing a temporary def at least once every few steps, otherwise the *n s might suffice.) (如果管道很长或者计算需要花费很多时间,我建议每隔几步至少进行一次临时def ,否则*n s就足够了。)

In other cases, you'd do something slightly different, but in any case breaking down the work into manageable chunks to be played with at the REPL is key. 在其他情况下,你会做一些稍微不同的事情,但无论如何将工作分解为可管理的块以便在REPL中播放是关键。 Of course familiarity with Clojure's sequence and collection libraries speeds the process up quite a bit; 当然,熟悉Clojure的序列和集合库会加快这个过程的速度; but then playing with them in the context of small chunks of an actual task you're working on is one of the better ways to learn about them. 但是在你正在进行的实际任务的小块环境中与它们一起玩是了解它们的更好方法之一。

The best way to make sense of clojure exceptions as of now (untill probably we have clojure in clojure) is to understand that clojure is implemented using Java classes,interfaces etc. So whenever you get any such exception try to map the classes/interfaces mentioned in the exception to clojure concepts. 现在理解clojure异常的最好方法(直到可能我们在clojure中使用clojure)是要理解clojure是使用Java类,接口等实现的。所以每当你得到任何这样的异常时,尝试映射提到的类/接口在clojure概念的例外。

For ex: In your current exception it can be easily inferred that clojure.lang.PersistentVector was being tried to type cast to java.lang.Number in the method clojure.lang.Numbers.add . 例如:在您当前的异常中,可以很容易地推断出clojure.lang.PersistentVector正在尝试在方法clojure.lang.Numbers.add中输入clojure.lang.Numbers.addjava.lang.Number From this information you can look into your code and intuitively figure out where you are using add ie + in your code and then diagnose that problem by the fact that somehow this + is getting vector as parameter instead of number. 根据这些信息,您可以查看代码并直观地找出您在代码中使用add ie +的位置,然后通过以下事实诊断该问题,即+以某种方式将vector作为参数而不是数字。

I find the clojure.tools.logging/spy macro very useful for debugging. 我发现clojure.tools.logging / spy宏对于调试非常有用。 It prints out the wrapped expression as well as its value. 它打印出包装的表达式及其值。 If setting up clojure.tools.logging isn't something you want to do right now (it requires the normal Java logging configurations) you can use this: 如果你现在不想设置clojure.tools.logging(它需要正常的Java日志记录配置),你可以使用:

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

*keep in mind that code above does not print out values of a lazy seq if it hasn't been realized. *请记住,如果尚未实现,则上面的代码不会打印出延迟seq的值。 You can vec a lazy seq to force its realization - not recommended for infinite seqs. 您可以VEC懒以次来强制实现-以不建议无限seqs。

Unfortunately, I haven't found a good way to use the spy macro within a threading macro, but it should suffice for most other cases. 不幸的是,我没有找到在线程宏中使用间谍宏的好方法,但它应该足以满足大多数其他情况。

Dynalint might be worth looking into. Dynalint可能值得研究。 It wraps function calls with extra checks that hurt performance, but provide better error messages. 它使用额外的检查来包装函数调用,这会影响性能,但会提供更好的错误消息。

It's doesn't seem to be a very mature project, and hasn't been updated for a year, but it already makes some progress to improve error messages. 它似乎不是一个非常成熟的项目,并且一年没有更新,但它已经在改进错误消息方面取得了一些进展。 Also, it is on the list for possible GSoC 2015 projects, so we might see a big improvement soon! 此外,它列在可能的GSoC 2015项目列表中,因此我们很快就会看到很大的改进!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM