在大多数情况下,我理解Clojure用它的错误信息告诉我什么。但我仍然无法找出错误发生的地方。
以下是我的意思
的示例(defn extract [m]
(keys m))
(defn multiple [xs]
(map #(* 2 %) xs))
(defn process [xs]
(-> xs
(multiple) ; seq -> seq
(extract))) ; map -> seq ... fails
(process [1 2 3])
静态类型语言现在告诉我,我试图将序列传递给期望X行上的地图的函数.Clojure以某种方式执行此操作:
ClassCastException java.lang.Long cannot be cast to java.util.Map$Entry
但我仍然不知道错误发生在哪里。显然,对于这个实例来说这很简单,因为只涉及3个函数,你可以轻松地阅读所有这些函数,但随着程序变大,这会很快变老。
有没有办法找出错误发生的地方,而不仅仅是从上到下证明读取代码? (这是我目前的做法)
答案 0 :(得分:3)
您可以使用clojure.spec。它仍处于alpha状态,并且仍有许多工具支持(希望如此),但仪器功能运行良好。
(ns foo.core
(:require
;; For clojure 1.9.0-alpha16 and higher, it is called spec.alpha
[clojure.spec.alpha :as s]
[clojure.spec.test.alpha :as stest]))
;; Extract takes a map and returns a seq
(s/fdef extract
:args (s/cat :m map?)
:ret seq?)
(defn extract [m]
(keys m))
;; multiple takes a coll of numbers and returns a coll of numbers
(s/fdef multiple
:args (s/cat :xs (s/coll-of number?))
:ret (s/coll-of number?))
(defn multiple [xs]
(map #(* 2 %) xs))
(defn process [xs]
(-> xs
(multiple) ; seq -> seq
(extract))) ; map -> seq ... fails
;; This needs to come after the definition of the specs,
;; but before the call to process.
;; This is something I imagine can be handled automatically
;; by tooling at some point.
(stest/instrument)
;; The println is to force evaluation.
;; If not it wouldn't run because it's lazy and
;; not used for anything.
(println (process [1 2 3]))
运行此文件打印(以及其他信息):
Call to #'foo.core/extract did not conform to spec: In: [0] val: (2
4 6) fails at: [:args :m] predicate: map? :clojure.spec.alpha/spec
#object[clojure.spec.alpha$regex_spec_impl$reify__1200 0x2b935f0d
"clojure.spec.alpha$regex_spec_impl$reify__1200@2b935f0d"]
:clojure.spec.alpha/value ((2 4 6)) :clojure.spec.alpha/args ((2 4
6)) :clojure.spec.alpha/failure :instrument
:clojure.spec.test.alpha/caller {:file "core.clj", :line 29,
:var-scope foo.core/process}
可以理解为:对exctract
的调用失败,因为(2 4 6)
中传递的值未通过谓词map?
。该调用发生在第29行的"core.clj"
文件中。
让人们吵架的一个警告是,仪器只检查函数参数而不返回值。这是Rich Hickey的设计决定(如果你问我的话很奇怪)。有a library for that, though。
答案 1 :(得分:0)
如果您有REPL会话,则可以打印堆栈跟踪:
(clojure.stacktrace/print-stack-trace *e 30)
有关打印堆栈跟踪的各种不同方法,请参阅http://puredanger.github.io/tech.puredanger.com/2010/02/17/clojure-stack-trace-repl/。您需要在project.clj
:
[org.clojure/tools.namespace "0.2.11"]
我没有使用上面的方法获得堆栈跟踪,但只需在REPL中键入*e
就可以获得有关错误的所有可用信息,说实话这似乎没什么用处。
对于堆栈跟踪无用的极少数情况,我通常使用对函数的调用来调试,该函数返回给出的单个参数,但是具有打印该参数的副作用。我碰巧称这个函数为probe
。在您的情况下,它可以放在线程宏中的多个位置。
答案 2 :(得分:0)
重新输入我的例子:
(defn extract [m]
(keys m))
(defn multiply [xs]
(mapv #(* 2 %) xs))
(defn process [xs]
(-> xs
(multiply) ; seq -> seq
(extract))) ; map -> seq ... fails ***line 21***
(println (process [1 2 3]))
;=> java.lang.ClassCastException: java.lang.Long cannot be cast
to java.util.Map$Entry, compiling:(tst/clj/core.clj:21:21)
因此,我们在异常中获得了一个很好的线索,其中tst.clj.core.clj:21:21
方法存在问题的文件和行/列号extract
。
我使用的另一个不可或缺的工具是Plumatic Schema,将“逐渐”类型检查注入clojure。代码变为:
(ns tst.clj.core
(:use clj.core tupelo.test)
(:require
[tupelo.core :as t]
[tupelo.schema :as tsk]
[schema.core :as s]))
(t/refer-tupelo)
(t/print-versions)
(s/defn extract :- [s/Any]
[m :- tsk/Map]
(keys m))
(s/defn multiply :- [s/Num]
[xs :- [s/Num]]
(mapv #(* 2 %) xs))
(s/defn process :- s/Any
[xs :- [s/Num]]
(-> xs
(multiply) ; seq -> seq
(extract))) ; map -> seq ... fails
(println (process [1 2 3]))
clojure.lang.ExceptionInfo: Input to extract does not match schema:
[(named (not (map? [2 4 6])) m)] {:type :schema.core/error, :schema [#schema.core.One{:schema {Any Any},
:optional? false, :name m}],
:value [[2 4 6]], :error [(named (not (map? [2 4 6])) m)]},
compiling:(tst/clj/core.clj:23:17)
因此,虽然错误消息的格式有点冗长,但它立即告诉我们将错误类型和/或形状的参数传递给方法extract
。
请注意,您需要这样一行:
(s/set-fn-validation! true) ; enforce fn schemas
我创建了一个特殊文件test/tst/clj/_bootstrap.clj
,因此它总是在同一个地方。
有关Plumatic Schema的更多信息,请参阅: