这个问题是在以下讨论语境中发现的
https://groups.google.com/d/msg/clojure/mIlKaOiujlo/tF71zZ2BCwAJ
规格错误报告失败的简单示例如下
他使用了一个无效的ns形式
(ns foo (require [clojure.spec :as s])) ; 应该是 :require
规格报告的错误
In: [1] val: ((require [clojure.spec :as s])) 失败在: [:args] 断言: (cat :docstring (? string?) :attr-map (? map?) :clauses :clojure.core.specs/ns-clauses), 额外输入
:clojure.spec/args (foo (require [clojure.spec :as s]))
clojure.core/ex-info (core.clj:4725)
虽然错误在技术上是真的,但它没有显示报告的每个替代方案的s/cat失败的方式。
为了更好地理解为什么用户数据不正确,他应该确切知道规格尝试了什么以及它是如何失败的。
一个很好的例子是s/alt,其中所有失败的替代方案总是报告给用户。
这个问题已经被调查,首先是通过实验,然后是规格代码。 最后,一个补丁用于带来类似于s/alts的错误报告。
观察到规格针对具有可选分支的cat的错误报告行为如下
1. 如果cat在经过一个或多个可选分支后失败,则会报告整个cat失败
2. 如果cat在经过一个或多个可选分支并且随后有一个必需分支后失败,则只报告后面的必需分支,并且没有提及替代的可选分支。
规则1解释了ns示例。
规则2可能严重违反用户的直觉
(s/explain (s/cat :maybe-num (s/? number?)
:keyword keyword?)
["3"])
给出
In: [0] val: "3" 失败在: [:keyword] 断言: keyword?
报告清楚地没有解决用户想要输入数字的意图。 相反,他被告知应该输入一个关键字。
解决方案
已经编写了一个简单的补丁,将op-explain更改为以下行为
- 在s/cat中报告所有尝试过的替代方案。
它显著改进了报告的错误,因为它清楚地显示了用户数据是如何验证失败的。
(ns foo (require [clojure.spec :as s])) ; 应该是 :require
现在给出
ExceptionInfo 对clojure.core/ns的调用未符合规格
In: [1] val: (require [clojure.spec :as s]) 失败在: [:args :docstring] 断言: string?
In: [1] val: (require [clojure.spec :as s]) 失败在: [:args :attr-map] 断言: map?
In: [1 0] val: require 失败规格: :clojure.core.specs/ns-refer-clojure 在: [:args :clauses :refer-clojure :clause] 断言: #{:refer-clojure}
输入:[1 0] val: require 失败规范::clojure.core.specs/ns-require 在:[:args :clauses :require :clause] 断言:#{:require}
输入:[1 0] val: require 失败规范::clojure.core.specs/ns-import 在:[:args :clauses :import :clause] 断言:#{:import}
输入:[1 0] val: require 失败规范::clojure.core.specs/ns-use 在:[:args :clauses :use :clause] 断言:#{:use}
输入:[1 0] val: require 失败规范::clojure.core.specs/ns-refer 在:[:args :clauses :refer :clause] 断言:#{:refer}
输入:[1 0] val: require 失败规范::clojure.core.specs/ns-load 在:[:args :clauses :load :clause] 断言:#{:load}
输入:[1 0] val: require 失败规范::clojure.core.specs/ns-gen-class 在:[:args :clauses :gen-class :clause] 断言:#{:gen-class}
:clojure.spec/args (foo (require [clojure.spec :as s]))
clojure.core/ex-info (core.clj:4725)
如果exlpain-data能够按:path的长度对::s/problems进行排序,那么将会把最初两个不必要的选项移动到后面。
(s/explain (s/cat :maybe-num (s/? number?)
:keyword keyword?)
["3"])
现在给出
输入:[0] val: "3" 失败在:[:maybe-num] 断言:number?
In: [0] val: "3" 失败在: [:keyword] 断言: keyword?
尽管可以构造出这种报告会产生更多噪音的例子(例如defn),但我相信这在上文所述原因是合理的
- 我们程序员总是在出错时向我们的用户请求最具体的信息 - 将此应用于规范错误报告是正确的
- 自定义错误报告器(s/*explain-out*)获取更多数据,以更好地匹配用户意图的窄报告