请在2024年Clojure状态调查!中分享您的想法。

欢迎!请参阅关于页面,了解更多关于此网站如何运作的信息。

+2
错误

Clojure堆栈跟踪相当晦涩,难以理解,是否有一个库可以用来显示更友好的错误或格式化堆栈跟踪?

2 个答案

+1

有许多库可以修改堆栈跟踪的打印方式,提供如折叠、Clojure框架和着色等功能。我们目前没有计划在下一个Clojure版本中做这方面的改进。

你能指向哪些这些库吗?
米歇尔,如果你还没有升级到 Clojure 1.10.1,我建议你升级。在 1.10 版本中对错误消息做了很多工作,并新增了更好的报告机制。

我还会提供关于 Stuart Halloway 的推特帖子链接,关于堆栈跟踪: https://twitter.com/stuarthalloway/status/1148295437448876032

当你刚开始学习 Clojure 时,堆栈跟踪可能会让你感到畏惧,但学会阅读它们是值得努力的,说实话,压缩/省略它们并不会像你想的那样有帮助(因为有时重要信息被这样的库隐藏起来了)。
当我知道有更好的错误消息时,这就是我认为在 1.10 中会发生的事情
https://reasonml.github.io/blog/2017/08/25/way-nicer-error-messages.html

例如,对于初学者来说,像 '''ClassCastException: java.lang.Long cannot be cast to clojure.lang.IFn''' 这样的错误是难以理解的,参见: https://stackoverflow.com/questions/26720847/classcastexception-java-lang-long-cannot-be-cast-to-clojure-lang-ifn

对我而言,最重要的增值在于能够指向用户的源代码中错误所在的行和列,从而快速显示错误在嵌套中的位置。

然后,用 Clojure 而不是 Java 术语更好地解释错误发生的原因,我真的不想听到关于类的问题,毕竟在我看来,我的代码中不存在类,即 (1 2 3)。

虽然如此,我认为这种情况的可行性很低。我们在这个语言中有很多互相操作的可能性,这可能会使这个过程比elm这样的封闭系统要难得多。
Clojure _可以_检测这些条件并抛出更具有信息量的异常,但这对所有人来说都会引入性能开销。历史上,选择一直是追求速度并忍受“不寻常”的Java异常,因为每个新来者只需学习这些“一次”——所以这对大多数用户来说是个不错的权衡。

尽管如此,如果有一个内置的“友好”的REPL选项,它使用:caught提供更友好的错误信息那将会很棒。
举个例子

user=> (defn easy [t]
                (clojure.main/repl-caught
                    (if (re-find #"cannot be cast to clojure.lang.IFn" (ex-message t))
                          (ex-info (str "Expected a function -- found: " (second (re-find #"^([^ ]*)" (ex-message t)))) {:cause t})
                    t)))
#'user/easy
user=> (clojure.main/repl :caught easy)
user=> (1 2 3)
执行错误(ExceptionInfo)在user/easy(REPL:1)处。
期望一个函数 - 发现:java.lang.Long
user=>
+1

这里是对更好错误信息的另一个+1。

下面提到的库在某种程度上成功地处理了症状,但让我们从想要学习Clojure的人的角度来看,因此前往Clojure.org,点击入门。

好的,我现在知道如何下载它了。也许我会尝试通过使用repl.it来感受一下。
现在我有了基本的经验(带有clojure.org中令人困惑的错误消息)(参见:“你可能会看到的令人困惑的错误之一是意外将数据列表作为代码尝试评估的结果:”)

那么,让我们先学习Clojure。
好的,仍然没有关于如何启动REPL的内容。
第三个链接:启动REPL是个好主意。
第四个链接:这里有n种启动REPL的方法。
这里有其他运行REPL的方法。

没有提到任何关于:如果你是初学者,让我们添加clj-stacktrace,然后启动REPL。
你看到为什么这不可行吗?

我知道你近年来投入了很多精力来改善文档,对此我感到很难过。很难回到如此熟悉的事物,并用一个想要学习这门新语言的开发者的视角来看它。

我相信你有1或可能2次机会来给人留下好印象,而且易于阅读的错误信息会告诉你哪里出错,这是其中重要的一部分。

user=> (map 1 inc)
不知道如何从:clojure.core$inc创建ISeq。

user=> (1 2 3)
执行错误(ClassCastException)在用户/eval7(REPL:1)。类java.lang.Long无法转换为类clojure.lang.IFn(java.lang.Long位于模块java.base中,加载器'bootstrap';clojure.lang.IFn位于未命名的模块中,加载器'app')

两者都没有告诉我我做了什么错。它告诉我一些关于背后如何工作的lisp解释器的具体信息。(其中一些在clojure.org上有文档记载)。

由于这些对我这个新用户没有任何帮助,我只好在谷歌上搜索。
对于像错误的参数顺序或调用无法调用的东西这样的错误,初学者对搜索错误信息的容忍度并不高。

这就是你给人留下错误信息对用户不重要的印象的地方。
(顺便说一句,你已经在处理(nil 2 3)方面做得很好,并没有抛出NPE)

这就是clojure(实现)积极地促成了“巨大的学习曲线”这个说法。
这并不重要,因为我看到这个错误足够多了,我知道它是什么。(关于Sean的评论)。

损害已经造成了。

其他Clojure方言在这方面做得更好。(例如CLJS和Sci)。

说到努力:有一个反对意见认为,提供更好的错误信息会影响到性能。如果我们在这里谈论异常处理(在上述两种情况中都这样做),
我不太明白性能影响在哪里。
因为s表达式失败而进行的异常处理不是性能关键路径,我想。
我很乐意更好地理解性能方面是如何介入的。

此外,仅就上面的两个示例错误信息而言,从用户的角度编写它们——而不是从系统的角度——的努力并不高。其中一个是改变RT.java第557行

更好的错误信息可能意味着捕获低级别的异常,将它们与当前的AST关联,并以与生成错误的消息关联的列表元素重新抛出。

我恳求您引导初学者进入他们能达到的最好的REPL。
使其成为入门和学习Clojure的默认设置。
然后你可以拉上帘子说:你知道,下面有IFn和ISeq,你可能会看到Java堆栈跟踪。

当走Clojure的道路时,有很多新的概念需要学习和理解。重新解释错误信息不应该是其中之一。

by
这是另一位Clojure初学者在ClassCastException上摔倒的另一个例子

https://www.reddit.com/r/Clojure/comments/gzudzn/problem_defining_fibonacci_function/
by
"尚无关于如何启动REPL的信息。" -- 我意识到(在看到你对此答案的最近评论后)安装说明中没有提及相关安装后的操作,因此我在“入门”页面提交了一个PR,建议在安装后在命令行运行clj或clojure以启动REPL。Alex已将其合并,所以希望这是一个小小的改进。
by
感谢,Sean。
对于一个现在熟悉这些概念的新手来说,重读文档并不是一件简单的事情。

沿着这个思路,也许在有文档直接在页面上收集反馈会很有用。许多公司试图改进面向非专家的文档时会这样做。也许这是一个比这个更好的反馈按钮:https://docs-feedback.aws.amazon.com/feedback.jsp?feedback_destination_id=068c0217-1615-4028-b3f7-7de2fb227b77&topic_url=https://aws.amazon.com/getting-started/hands-on/build-web-app-s3-lambda-api-gateway-dynamodb/module-one/
(例示)
...