(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
也会打印导致测试失败的值。
答案 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) => ""
编辑:启用断言检查。