这个问题是在这次讨论的背景下检测到的:
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]谓词: (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 对具有可选分支的 cat 的错误报告行为如下
1. 如果 cat 在一个或多个可选分支之后失败,则整个 cat 被报告为失败
2. 如果 cat 在一个或多个可选分支后失败,并且有一位后续的必选分支,则仅报告后续的必选分支,并且对替代的可选分支没有任何说明。
规则 1 解释了 ns 的例子。
规则 2 可能会严重违反用户的直觉
(s/explain (s/cat :maybe-num (s/? number?)
:keyword keyword?)
["3"])
给出了
在: [0] val: "3" 失败在: [:keyword]谓词: 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]谓词: string?
在: [1] val: (require [clojure.spec :as s]) 失败在: [:args :attr-map]谓词: map?
在: [1 0] val: require 失败 spec: :clojure.core.specs/ns-refer-clojure 在: [:args :clauses :refer-clojure :clause]谓词: #{:refer-clojure}
在: [1 0] val: require 失败 spec: :clojure.core.specs/ns-require 在: [:args :clauses :require :clause]谓词: #{:require}
输入:[1 0] 值:require失败规范::clojure.core.specs/ns-import 位置:[:args:clauses:import:clause] 判断条件:#{:import}
输入:[1 0] 值:require失败规范::clojure.core.specs/ns-use 位置:[:args:clauses:use:clause] 判断条件:#{:use}
输入:[1 0] 值:require失败规范::clojure.core.specs/ns-refer 位置:[:args:clauses:refer:clause] 判断条件:#{:refer}
输入:[1 0] 值:require失败规范::clojure.core.specs/ns-load 位置:[:args:clauses:load:clause] 判断条件:#{:load}
输入:[1 0] 值: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] 值:“3”失败位置:[:maybe-num] 判断条件:number?
在: [0] val: "3" 失败在: [:keyword]谓词: keyword?
虽然可以举例说明这种报告会产生更多的噪音(例如defn),但在我看来,这是上述原因的正确权衡。
- 当有问题发生时,我们程序员总是要求用户提供最具体的信息 - 对于规格错误报告来说,这样做是正确的
- 定制错误报告器(s/*explain-out*)获取更多数据,生成更符合用户意图的狭窄报告