我如何得到Clojure:pre& :发布报告失败的价值?

时间:2014-07-18 21:33:38

标签: clojure clojurescript

(defn string-to-string [s1] 
  {:pre  [(string? s1)]
   :post [(string? %)]}
  s1)

我喜欢:pre和:post条件,它们让我能够更快地找出“圆孔中的方形钉子”的时间。也许这是错误的,但我喜欢将它们用作一种可怜的勒芒式检查器。但这不是哲学,这是一个简单的问题。

在上面的代码中,我应该能够轻松地确定s1:pre条件中的函数参数。类似地,%条件中的:post始终是函数返回值。

我想要的是在AssertionError中这些相应条件中的任何一个失败时打印s1%的值。所以我得到像

这样的东西
(string-to-string 23)

AssertionError Assert failed: (string? s1) 
(pr-str s1) => 23 

AssertionError包含每个变量的单行,该变量被识别为来自函数参数列表并且在失败测试中被引用。当函数的返回值未通过:post条件时,我也想要类似的东西。

在尝试从AssertionError进行诊断时,这样可以快速发现我滥用函数的方法很简单。如果值为nil或实际值(这是我犯的最常见错误),它至少会让我知道。

我有一些想法可以通过宏来完成,但我想知道是否有任何安全和全局的方式来基本上重新定义(defn(fn以及朋友这样做:pre {1}}和:post也会打印导致测试失败的值。

4 个答案:

答案 0 :(得分:27)

您可以使用clojure.test

中的is宏来包装谓词
(defn string-to-string [s1] 
  {:pre  [(is (string? s1))]
   :post [(is (string? %))]}
 s1)

然后你得到:

(string-to-string 10)
;FAIL in clojure.lang.PersistentList$EmptyList@1 (scratch.clj:5)
;expected: (string? s1)
;actual: (not (string? 10))

答案 1 :(得分:12)

@octopusgrabbus通过提出(try ... (catch ...))暗示了这一点,你提到那可能太吵了,并且仍然包含在一个断言中。一个更简单且噪声更小的变体是简单的(or (condition-here) (throw-exception-with-custom-message))语法,如下所示:

(defn string-to-string [s1] 
  {:pre  [(or (string? s1)
              (throw (Exception. (format "Pre-condition failed; %s is not a string." s1))))]
   :post [(or (string? %)
              (throw (Exception. (format "Post-condition failed; %s is not a string." %))))]}
  s1)

这实际上允许您使用自定义错误消息的前置条件和后置条件 - 仍然像往常一样检查前置和后置条件,但是在AssertionError之前评估(并因此抛出)自定义异常可能会发生。

答案 2 :(得分:2)

如下所示,clojure规范解释了这个问题?这将抛出一个你可以捕获的断言错误。

 (defn string-to-string [s1] 
  {:pre [ (or (s/valid?  ::ur-spec-or-predicate s1) 
              (s/explain ::ur-spec-or-predicate s1)]}
  s1)

答案 3 :(得分:2)

Clojure规范可用于对参数进行断言,对无效输入产生异常,并提供解释失败原因的数据(必须启用断言检查):

(require '[clojure.spec.alpha :as s])

;; "By default assertion checking is off - this can be changed at the REPL
;;  with s/check-asserts or on startup by setting the system property
;;  clojure.spec.check-asserts=true"
;;
;; quoted from https://clojure.org/guides/spec#_using_spec_for_validation
(s/check-asserts true)

(defn string-to-string [s1] 
  {:pre  [(s/assert string? s1)]
   :post [(s/assert string? %)]}
  s1)

(string-to-string nil) => #error{:cause "Spec assertion failed\nnil - failed: string?\n",
                                 :data #:clojure.spec.alpha{:problems [{:path [], :pred clojure.core/string?, :val nil, :via [], :in []}],
                                                            :spec #object[clojure.core$string_QMARK___5395 0x677b8e13 "clojure.core$string_QMARK___5395@677b8e13"],
                                                            :value nil,
                                                            :failure :assertion-failed}}

异常中的[:data :value]键会向您显示失败的值。 [:data :problems]键向您显示为什么规范认为该值无效。 (在此示例中,问题很直接,但是当您将嵌套地图和多个规格组合在一起时,此说明将非常有用。)

一个重要的警告是,s/assert在给定有效输入时会返回该输入,但是:pre:post条件检查真实性。如果您需要验证条件认为伪造的值有效,那么您需要调整验证表达式,否则s/assert将成功,但:pre:post中的真实性检查将失败。

(defn string-or-nil-to-string [s1]
  {:pre [(s/assert (s/or :string string? :nil nil?) s1)]
   :post [(s/assert string? %)]}
  (str s1))

(string-or-nil-to-string nil) => AssertionError

这就是我用来避免该问题的方法:

(defn string-or-nil-to-string [s1]
  {:pre [(do (s/assert (s/or :string string? :nil nil?) s1) true)]
   :post [(s/assert string? %)]}
  (str s1))

(string-or-nil-to-string nil) => ""

编辑:启用断言检查。