此问题是在以下讨论的上下文中发现的:
https://groups.google.com/d/msg/clojure/mIlKaOiujlo/tF71zZ2BCwAJ
一个简化的如何导致规格错误报告失败的示例如下
他使用了一个无效的命名空间形式
(ns foo (require [clojure.spec :as s])) ; 应该是 :require
由规格报告的错误
在: [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?)
["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] 值: require 失败 规范: :clojure.core.specs/ns-refer-clojure 在: [:args :clauses :refer-clojure :clause] 断言: #{:refer-clojure}
输入: [1 0] 值: 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)
如果是解释程序按照 ::s/problems 中 :path 的长度排序会更好,这将把前两个未预期选项推到最后。
(s/explain (s/cat :maybe-num (s/? number?)
["3"])
现在给出
输入: [0] 值: "3" 失败 在: [:maybe-num] 断言: number?
在: [0] val: "3" 失败在: [:keyword] 判别符: keyword?
虽然可以构建一些例子,其中这种报告会产生更多噪音(比如 defn),但我认为上述原因是正确的折衷方案。
- 我们程序员在出错时总是要求用户提供最具体的信息 - 将此应用于规格错误报告是正确的
- 自定义错误报告程序(s/*explain-out*)获得更多数据来生成更符合用户意图的窄报告