目前(1.10.0-RC5)堆栈跟踪看起来像这样
user=> (Exception.)
#error {
:cause nil
:via
[{:type java.lang.Exception
:message nil
:at [user$eval3 invokeStatic "NO_SOURCE_FILE" 1]}]
:trace
[[user$eval3 invokeStatic "NO_SOURCE_FILE" 1]
[user$eval3 invoke "NO_SOURCE_FILE" 1]
[clojure.lang.Compiler eval "Compiler.java" 7176]
[clojure.lang.Compiler eval "Compiler.java" 7131]
[clojure.core$eval invokeStatic "core.clj" 3214]
[clojure.core$eval invoke "core.clj" 3210]
[clojure.main$repl$read_eval_print__9068$fn__9071 invoke "main.clj" 414]
[clojure.main$repl$read_eval_print__9068 invoke "main.clj" 414]
[clojure.main$repl$fn__9077 invoke "main.clj" 435]
[clojure.main$repl invokeStatic "main.clj" 435]
[clojure.main$repl_opt invokeStatic "main.clj" 499]
[clojure.main$main invokeStatic "main.clj" 598]
[clojure.main$main doInvoke "main.clj" 561]
[clojure.lang.RestFn invoke "RestFn.java" 397]
[clojure.lang.AFn applyToHelper "AFn.java" 152]
[clojure.lang.RestFn applyTo "RestFn.java" 132]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.main main "main.java" 37]]}
这些是Java堆栈跟踪,即使在Clojure内部运行的代码也是如此。我相信,许多开发人员将受益于以Clojure感知的方式渲染源自Clojure的堆栈跟踪元素。例如
[clojure.core/eval "core.clj" 3214]
[clojure.main/repl/read-eval-print "main.clj" 414]
[clojure.main/repl "main.clj" 435]
[clojure.main/repl-opt "main.clj" 499]
[clojure.main/main "main.clj" 598]
[clojure.main/main "main.clj" 561]
主要区别
- 名字去混淆(用户看到的名字与Clojure文件中相同,而不是它们被编译到Java代码的方式。我相信并非每个Clojure用户都需要知道那部分内容)
- 折叠了重复的调用/invokeStatic/doInvoke(这再次是一个实现细节,从Clojure用户调用方法是单个操作。因此,许多堆栈跟踪可能会变得相当短)
我相信还有许多其他实现细节,例如协议调用、多重方法调用、匿名函数,编译细节会溢出到堆栈跟踪中,导致堆栈跟踪变得很大,只有编译器专家可以阅读。尽管在这个任务中没有指定,但这些也应该是折叠成Clojure友好的表示形式。
还有很多地方应该应用此类表示形式
- 默认异常打印程序
- clojure.main
- clojure.repl
- clojure.stacktrace(由clojure.test使用)
也许所有这些地方都需要某种统一?例如,为什么clojure.core中有三个{{(root-cause)}}实现?
前期工作:我相信{{(pst)}}做一些基本的去混淆,但不折叠。它也不处理协议等复杂情况。并且必须显式调用,这并不方便。