此问题是在以下讨论中发现的
https://groups.google.com/d/msg/clojure/mIlKaOiujlo/tF71zZ2BCwAJ
规范错误报告失败并导致用户直觉上的错误的简化版本如下
他使用了一个无效的 ns 形式
(ns foo (require [clojure.spec :as s])) ; 应该是 :require
规范报告的错误
在:[1] val:((require [clojure.spec :as s])) 在 [:args] 处失败,谓词:(: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"])
给出
在:[0] val:"3" 在 [:keyword] 处失败,谓词:keyword?
报告清楚地没有解决用户输入数字的意图。相反,他被告知应该输入一个关键字。
解决方案
已编写一个简单的补丁,该补丁将 op-explain 更改为以下行为
- 在 s/cat 中报告所有尝试过的替代选项。
因为它清楚地说明了用户数据如何失败验证,所以这显著提高了报告错误的质量。
(ns foo (require [clojure.spec :as s])) ; 应该是 :require
现在给出
ExceptionInfo 对 clojure.core/ns 的调用不符合规范
在:[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 失败规范::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] 值: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 可以根据 ::s/problems 的 :path 长度进行排序,那就更好了,这样可以将前两个不合理的选项移到末尾。
(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*)获得更多数据,以便生成更符合用户意图的窄报告