Clojure.Spec验证的有意义的错误消息:pre

时间:2016-06-17 15:43:31

标签: clojure clojure.spec

我用了最后几天深入挖掘Clojure和ClojureScript中的 clojure.spec

到目前为止,我觉得最有用的是,在依赖于某种格式的数据的公共函数中,使用规范作为:pre:post中的警卫。

(defn person-name [person]
  {:pre [(s/valid? ::person person)]
   :post [(s/valid? string? %)]}
  (str (::first-name person) " " (::last-name person)))

这种方法的问题是,我得到的java.lang.AssertionError: Assert failed: (s/valid? ::person person)没有任何关于究竟没有达到规范的信息。

:pre:post警卫中,是否有人如何获得更好的错误消息

我了解conformexplain*,但这对那些:pre:post警卫没有帮助。

4 个答案:

答案 0 :(得分:8)

在较新的alpha中,现在有s/assert可用于断言输入或返回值与规范匹配。如果有效,则返回原始值。如果无效,则会在解释结果中引发断言错误。断言可以打开或关闭,甚至可以选择从编译的代码中省略,以产生0生产影响。

(s/def ::first-name string?)
(s/def ::last-name string?)
(s/def ::person (s/keys :req [::first-name ::last-name]))
(defn person-name [person]
  (s/assert ::person person)
  (s/assert string? (str (::first-name person) " " (::last-name person))))

(s/check-asserts true)

(person-name 10)
=> CompilerException clojure.lang.ExceptionInfo: Spec assertion failed
val: 10 fails predicate: map?
:clojure.spec/failure  :assertion-failed
 #:clojure.spec{:problems [{:path [], :pred map?, :val 10, :via [], :in []}], :failure :assertion-failed}

答案 1 :(得分:6)

我认为您的想法是使用curl -XPOST localhost:9200/sentimentresult/sentiment/_bulk -d ' {"index":{"_id":"1"}} {"Positivity_score":55.17999999,"Negativity_score":35.12,"Average_Positivity":0.8621874999999999,"Average_Negativity":0.54875} {"index":{"_id":"2"}} {"Positivity_score":134.71999999999997,"Negativity_score":90.08000000000017,"Average_Positivity":0.8419999999999999, "Average_Negativity":0.5701265822784821} {"index":{"_id":"3"}} {"Positivity_score":44.800000000000004,"Negativity_score":28.799999999999986,"Average_Positivity":0.7344262295081968, "Average_Negativity":0.47999999999999976} ' 来验证函数输入和输出,而不是前后条件。

这篇博文的底部有一个很好的例子:http://gigasquidsoftware.com/blog/2016/05/29/one-fish-spec-fish/。快速摘要:您可以使用:args和:ret键定义一个函数的规范,包括输入和返回值(因此替换前置条件和后置条件),使用spec/instrument,检测它,然后输出类似于使用spec/fdef时无法满足规范。

从该链接派生的最小示例:

explain

这等同于放置一个前提条件,即函数有一个整数参数和一个后置条件,它返回一个字符串。除非你得到更多有用的错误,就像你正在寻找的那样。

官方指南中的更多详细信息:https://clojure.org/guides/spec ---请参阅标题“规范功能”。

答案 2 :(得分:2)

如果不考虑是否应使用前置和后置条件来验证函数参数,可以通过使用clojure.test/is包装谓词来打印来自前置和后置条件的更清晰的消息,如答案中所建议的那样下面:

How can I get Clojure :pre & :post to report their failing value?

那么你的代码看起来像这样:

(ns pre-post-messages.core
  (:require [clojure.spec :as s]
            [clojure.test :as t]))

(defn person-name [person]
  {:pre [(t/is (s/valid? ::person person))]
   :post [(t/is (s/valid? string? %))]}
  (str (::first-name person) " " (::last-name person)))

(def try-1
  {:first-name "Anna Vissi"})

(def try-2
  {::first-name "Anna"
   ::last-name "Vissi"
   ::email "Anna@Vissi.com"})

(s/def ::person (s/keys :req [::first-name ::last-name ::email]))

评估

pre-post-messages.core> (person-name  try-2)

会产生

"Anna Vissi"

并评估

pre-post-messages.core> (person-name  try-1)

会产生

FAIL in () (core.clj:6)

expected: (s/valid? :pre-post-messages.core/person person)

  actual: (not (s/valid? :pre-post-messages.core/person {:first-name "Anna Vissi"}))

AssertionError Assert failed: (t/is (s/valid? :pre-post-messages.core/person person))  pre-post-messages.core/person-name (core.clj:5)

答案 3 :(得分:0)

当您不想使用s/assert或无法启用s/check-assserts时,这很有用。改善MicSokoli的答案:

:pre只是在乎返回的值都是真实的,因此我们可以将返回值"Success!\n"转换为true(出于严格性考虑),而throw则将错误说明和输入数据,以防输出不成功。

(defn validate [spec input]
    (let [explanation (s/explain-str spec input)]
        (if (= explanation "Success!\n") 
            true
            (throw (ex-info explanation {:input input}))))

这可能是该版本的一种变体,但它将运行两次规范:

(defn validate [spec input]
    (if (s/valid? spec input) 
        true
        (throw (ex-info (s/explain spec input) {:input input}))))

用法:

(defn person-name [person]
  {:pre [(validate ::person person)]}
  (str (::first-name person) " " (::last-name person)))