(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) => ""
编辑:启用断言检查