我怎样才能得到Clojure:pre &:post以报告失败值


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

我喜欢:pre和:post条件,它们可以让我更快地弄清楚我什么时候把"方子插在圆孔里"。也许这是错误的,但我喜欢把它们当作一种穷人的打字检查器。这不是哲学,这是一个简单的问题。

在上面的代码中,我似乎可以很容易地确定s1:pre条件下的函数参数。同理,:post条件下的%始终是函数的返回值。

我想要的是打印s1%的值,当这些各自的条件在AssertionError中失败时。所以我得到了像

这样的东西
(string-to-string 23)
AssertionError Assert failed: (string? s1) 
(pr-str s1) => 23 

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

当尝试从AssertionError进行诊断时,这将使快速发现如何滥用函数变得很简单。它至少可以让我知道该值是nil还是实际值(这是我最常犯的错误)。

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

可以用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))

@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发生之前被计算(并因此被抛出)。

类似下面clojure spec解释问题的地方?这将抛出一个可以捕获的断言错误。

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

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

(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 failednnil - 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]键显示了spec认为该值无效的原因。(在这个例子中,问题是直接的,但是当你有嵌套的映射和多个规范组合在一起时,这个解释非常有用。)

一个重要的警告是,当给定有效输入时,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) => ""

编辑:启用断言检查

最新更新