该问题在以下讨论中检测到:
https://groups.google.com/d/msg/clojure/mIlKaOiujlo/tF71zZ2BCwAJ
Specs 错误报告失败的示例最少版本如下
使用了无效的 ns 形式
(ns foo (require [clojure.spec :as s])) ; 应为 :require
Spec 报告的错误
在:[1] val: ((require [clojure.spec :as s])) 在:[:args] 上失败 predicate: (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 的每个替代选项是如何失败。
为了更好地理解为什么用户数据不正确,他应该确切知道 spec 尝试了什么以及它是如何失败的。
这是一个很好说明如何工作的例子,即 s/alt,其中所有失败的替代项总是报告给用户。
这个问题已经被调查过,首先是实验性的,然后在 specs 代码中。 最后,附上一个修复程序,类似于 s/alts 的错误报告。
观察到的 specs 错误报告行为如下
1. 如果 cat 在一个或多个可选分支后失败,则报告整个 cat 失败
2. 如果 cat 在一个或多个可选分支后 /并且/一个随后的必需分支失败,则只报告随后的必需分支,不对替代的可选分支做任何备注。
规则 1 解释了 ns 的示例。
规则 2 可能会严重影响用户的直觉
(s/explain (s/cat :maybe-num (s/? number?)
:keyword keyword?)
["3"])
给出
在:[0] val: "3" 在:[:keyword] 上失败 predicate: keyword?
报告明确没有解决用户想要输入数字的问题。 相反,他可能会认为他应该输入一个关键字。
解决方案
已编写一个简单的补丁,改变 op-explain 以具备以下行为
- s/cat 中尝试的所有替代选项都要单独报告。
它显著提高了报告的错误,因为它清楚地说明了用户数据是如何未通过验证的。
(ns foo (require [clojure.spec :as s])) ; 应为 :require
现在给出
ExceptionInfo 调用 clojure.core/ns 未符合 spec
在:[1] val: (require [clojure.spec :as s]) 在:[:args :docstring] 上失败 predicate: string?
在:[1] val: (require [clojure.spec :as s]) 在:[:args :attr-map] 上失败 predicate: map?
在:[1 0] val: require 未通过 spec::clojure.core.specs/ns-refer-clojure 在:[:args :clauses :refer-clojure :clause] 上失败 predicate: #{: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)
如果explain-data能按它们:path的长度对::s/problems排序就更好了,这样会将前两个不期望的选项推向末尾。
(s/explain (s/cat :maybe-num (s/? number?)
:keyword keyword?)
["3"])
现在给出
输入:[0] val:"3"失败位置:[:maybe-num]谓词:number?
在:[0] val: "3" 在:[:keyword] 上失败 predicate: keyword?
虽然可以编造出这种报告会产生更多噪音的例子(如defn),但我相信考虑到上述原因,这是一个正确的权衡。
—— 我们程序员在出错时总是要求用户提供尽可能详细的信息 - 将同样的应用适用于规格错误报告是正确的
—— 自定义错误报告器(s/*explain-out*)可以获得更多数据,以便更好地生成符合用户意图的狭窄报告