【问题标题】:Why are Clojure stacktraces so long?为什么 Clojure 堆栈跟踪这么长?
【发布时间】:2013-01-12 19:27:31
【问题描述】:

我过去写过一些玩具解释器/编译器,所以我将编译器源文件中引用行的堆栈跟踪与编译器错误联系起来。

如果我将以下错误函数定义添加到我的 compojure 项目中:

(defn dodgy-map []
  {:1 :2 :3})

Lein 拒绝开始:

$ lein ring server-headless
Exception in thread "main" java.lang.RuntimeException: Map literal must contain an even number of forms, compiling:(one_man_wiki/views.clj:19)
        at clojure.lang.Compiler.load(Compiler.java:6958)
        at clojure.lang.RT.loadResourceScript(RT.java:359)
        at clojure.lang.RT.loadResourceScript(RT.java:350)
        at clojure.lang.RT.load(RT.java:429)
        at clojure.lang.RT.load(RT.java:400)
        at clojure.core$load$fn__4890.invoke(core.clj:5415)
        at clojure.core$load.doInvoke(core.clj:5414)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invoke(core.clj:5227)
        at clojure.core$load_lib.doInvoke(core.clj:5264)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$load_libs.doInvoke(core.clj:5298)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$require.doInvoke(core.clj:5381)
        at clojure.lang.RestFn.invoke(RestFn.java:457)
        at one_man_wiki.handler$eval1564$loading__4784__auto____1565.invoke(handler.clj:1)
        at one_man_wiki.handler$eval1564.invoke(handler.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:6511)
        at clojure.lang.Compiler.eval(Compiler.java:6501)
        at clojure.lang.Compiler.load(Compiler.java:6952)
        at clojure.lang.RT.loadResourceScript(RT.java:359)
        at clojure.lang.RT.loadResourceScript(RT.java:350)
        at clojure.lang.RT.load(RT.java:429)
        at clojure.lang.RT.load(RT.java:400)
        at clojure.core$load$fn__4890.invoke(core.clj:5415)
        at clojure.core$load.doInvoke(core.clj:5414)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invoke(core.clj:5227)
        at clojure.core$load_lib.doInvoke(core.clj:5264)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$load_libs.doInvoke(core.clj:5298)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$require.doInvoke(core.clj:5381)
        at clojure.lang.RestFn.invoke(RestFn.java:421)
        at user$eval1.invoke(NO_SOURCE_FILE:1)
        at clojure.lang.Compiler.eval(Compiler.java:6511)
        at clojure.lang.Compiler.eval(Compiler.java:6500)
        at clojure.lang.Compiler.eval(Compiler.java:6477)
        at clojure.core$eval.invoke(core.clj:2797)
        at clojure.main$eval_opt.invoke(main.clj:297)
        at clojure.main$initialize.invoke(main.clj:316)
        at clojure.main$null_opt.invoke(main.clj:349)
        at clojure.main$main.doInvoke(main.clj:427)
        at clojure.lang.RestFn.invoke(RestFn.java:421)
        at clojure.lang.Var.invoke(Var.java:419)
        at clojure.lang.AFn.applyToHelper(AFn.java:163)
        at clojure.lang.Var.applyTo(Var.java:532)
        at clojure.main.main(main.java:37)
Caused by: java.lang.RuntimeException: Map literal must contain an even number of forms
        at clojure.lang.Util.runtimeException(Util.java:170)
        at clojure.lang.LispReader$MapReader.invoke(LispReader.java:1071)
        at clojure.lang.LispReader.readDelimitedList(LispReader.java:1126)
        at clojure.lang.LispReader$ListReader.invoke(LispReader.java:962)
        at clojure.lang.LispReader.read(LispReader.java:180)
        at clojure.lang.Compiler.load(Compiler.java:6949)
        ... 51 more

如果我引用了一个不存在的变量:

(defn no-such-variable []
  i-dont-exist)

我得到了同样巨大的回溯:

Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol: i-dont-exist in this context, compiling:(one_man_wiki/views.clj:18)
        at clojure.lang.Compiler.analyze(Compiler.java:6281)
        at clojure.lang.Compiler.analyze(Compiler.java:6223)
        at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:5618)
        at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5054)
        at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3674)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6453)
        at clojure.lang.Compiler.analyze(Compiler.java:6262)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6443)
        at clojure.lang.Compiler.analyze(Compiler.java:6262)
        at clojure.lang.Compiler.access$100(Compiler.java:37)
        at clojure.lang.Compiler$DefExpr$Parser.parse(Compiler.java:518)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6455)
        at clojure.lang.Compiler.analyze(Compiler.java:6262)
        at clojure.lang.Compiler.analyze(Compiler.java:6223)
        at clojure.lang.Compiler.eval(Compiler.java:6515)
        at clojure.lang.Compiler.load(Compiler.java:6952)
        at clojure.lang.RT.loadResourceScript(RT.java:359)
        at clojure.lang.RT.loadResourceScript(RT.java:350)
        at clojure.lang.RT.load(RT.java:429)
        at clojure.lang.RT.load(RT.java:400)
        at clojure.core$load$fn__4890.invoke(core.clj:5415)
        at clojure.core$load.doInvoke(core.clj:5414)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invoke(core.clj:5227)
        at clojure.core$load_lib.doInvoke(core.clj:5264)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$load_libs.doInvoke(core.clj:5298)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$require.doInvoke(core.clj:5381)
        at clojure.lang.RestFn.invoke(RestFn.java:457)
        at one_man_wiki.handler$eval1564$loading__4784__auto____1565.invoke(handler.clj:1)
        at one_man_wiki.handler$eval1564.invoke(handler.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:6511)
        at clojure.lang.Compiler.eval(Compiler.java:6501)
        at clojure.lang.Compiler.load(Compiler.java:6952)
        at clojure.lang.RT.loadResourceScript(RT.java:359)
        at clojure.lang.RT.loadResourceScript(RT.java:350)
        at clojure.lang.RT.load(RT.java:429)
        at clojure.lang.RT.load(RT.java:400)
        at clojure.core$load$fn__4890.invoke(core.clj:5415)
        at clojure.core$load.doInvoke(core.clj:5414)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invoke(core.clj:5227)
        at clojure.core$load_lib.doInvoke(core.clj:5264)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$load_libs.doInvoke(core.clj:5298)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invoke(core.clj:603)
        at clojure.core$require.doInvoke(core.clj:5381)
        at clojure.lang.RestFn.invoke(RestFn.java:421)
        at user$eval1.invoke(NO_SOURCE_FILE:1)
        at clojure.lang.Compiler.eval(Compiler.java:6511)
        at clojure.lang.Compiler.eval(Compiler.java:6500)
        at clojure.lang.Compiler.eval(Compiler.java:6477)
        at clojure.core$eval.invoke(core.clj:2797)
        at clojure.main$eval_opt.invoke(main.clj:297)
        at clojure.main$initialize.invoke(main.clj:316)
        at clojure.main$null_opt.invoke(main.clj:349)
        at clojure.main$main.doInvoke(main.clj:427)
        at clojure.lang.RestFn.invoke(RestFn.java:421)
        at clojure.lang.Var.invoke(Var.java:419)
        at clojure.lang.AFn.applyToHelper(AFn.java:163)
        at clojure.lang.Var.applyTo(Var.java:532)
        at clojure.main.main(main.java:37)
Caused by: java.lang.RuntimeException: Unable to resolve symbol: i-dont-exist in this context
        at clojure.lang.Util.runtimeException(Util.java:170)
        at clojure.lang.Compiler.resolveIn(Compiler.java:6766)
        at clojure.lang.Compiler.resolve(Compiler.java:6710)
        at clojure.lang.Compiler.analyzeSymbol(Compiler.java:6671)
        at clojure.lang.Compiler.analyze(Compiler.java:6244)
        ... 66 more

为什么 Clojure 编译器不产生一个 ClojureSyntaxError 和一个 ClojureNameError 可以在顶层捕获并显示一个简单的错误?这些是开发过程中常见的开发者错误。

如果长回溯在某些情况下有用,为什么要截断它们?

编辑:我正在寻找答案:

  1. 在获取引用 clojure.lang 的回溯时是否有用?
  2. (相关)是否有任何技术限制阻止添加ClojureSyntaxError,如上所述?值得我在 Clojure 错误跟踪器上打开一个问题吗? (更新:我有opened CLJ-1155
  3. 有经验的 Clojure 程序员如何阅读这些回溯?有什么工具可以帮忙吗?每个人都使用 clj-stacktrace 吗?

【问题讨论】:

  • 我同意,Clojure 在某些方面确实与 C++ 竞争。
  • 您能否编辑问题以更清楚地解释可接受的答案是什么样的?
  • mods - 虽然它当然可以改进,但恕我直言的问题是合理的,可以建设性地回答(正如 JohnJ 在下面演示的那样)。投票决定重新开放。
  • +1 表示重新开放,注意到迄今为止的大量问题投票。

标签: clojure


【解决方案1】:

回复您的编号,

  1. 在 Clojure 中直接与 Java 库进行互操作是惯用的做法,因此如果您以某种意外或不受支持的方式调用 Java 对象,则获取完整的 Java 堆栈跟踪会很有帮助。

  2. 听起来是个好主意;我经常希望至少有一个可设置的选项,它可以让我只看到源自我自己的代码的堆栈跟踪部分,抑制所有底层语言层。

  3. 我通常只是以艰难的方式进行并仔细研究堆栈跟踪以获取我的程序中出现的行,调出clojure.* 部分(我通常测试每一分钟的更改,所以我很清楚什么更改导致问题)。我使用的一些 Emacs 和 Eclipse 工具只向您显示实际错误,而不是整个堆栈跟踪;我通常觉得这更有帮助。

    在 2012 年的 Clojure/west 上,@chouser 提供了 a nice talk [PDF] 关于堆栈跟踪的剖析,解释了如何阅读它们,并介绍了一个看起来很有前途的工具,显然还没有看到曙光.

与我觉得 Python 的堆栈跟踪非常用户友好的 Python 相比,我认为堆栈跟踪在 Clojure 中是一个粗略的优势,尤其是对于初学者而言。这部分是由于该语言的“托管”性质,尽管我希望可以在不增加附带复杂性的情况下进行改进。

【讨论】:

  • 一些 Clojure 工具现在过滤堆栈跟踪以降低它们的噪音,例如the expectations test library.
  • @WilfredHughes - 好点; Emacs 中最新的 CIDER 集成还提供了按类型(Clojure、Java、REPL...)选择性过滤堆栈帧。
【解决方案2】:

Clojure 堆栈跟踪很嘈杂,这显然是一种抱怨。一些库提供了更好的堆栈跟踪,例如 clj-stacktraceexpectations library

但 Clojure 本身也在改进这一点,例如 recent 1.7 release。另请参阅clojure.stacktrace api

所以我敢打赌,随着时间的推移,它只会变得更好。 Clojure{Script} 生态系统中的事情现在非常令人兴奋。

【讨论】:

    猜你喜欢
    • 2015-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-26
    • 1970-01-01
    • 2014-07-27
    • 1970-01-01
    相关资源
    最近更新 更多