【问题标题】:How can I get Clojure :pre & :post to report their failing value?如何让 Clojure :pre & :post 报告它们的失败值?
【发布时间】:2014-07-18 21:33:37
【问题描述】:
(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 和 @ 987654335@ 还会打印导致测试失败的值。

【问题讨论】:

  • 我可能遗漏了一些关于您的问题的内容,但是 try .. catch 是否允许在 :pre 或 :post 中使用?你不能这样报告问题吗?
  • 我认为任何有效的形式都可以在 :pre 和 :post 中使用(虽然不确定)。我要指出 :pre 和 :post 中的形式似乎被插入到断言中。所以,你可以 (try (catch...)),但结果是 (assert (try (catch...)));没关系(虽然有点吵 & 也只能在实际断言之前打印)。不过,我的目标是实际修改底层 AssertError 本身,只需在 AssertionError 中添加相关符号和(pr-str 值)作为消息。

标签: clojure clojurescript


【解决方案1】:

您可以使用来自clojure.testis 宏来包装您的谓词

(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))

【讨论】:

  • 相当不错,我喜欢。这是一个合理的妥协,让我达到了 90%。不过,我认为如果在 Clojure 1.7+ 中,他们只是让它自动打印出所有立即变量的值(认识到 refs、惰性序列以及我没有考虑的其他可能会导致问题的事情),那就太好了。
  • 因为这对于编码和在输出中查看都有些笨拙,我认为这可以通过 clojure 的核心本身变得更简单。也许clojure.spec 提供的东西比:pre 更整洁。
  • 我只是收到Assert failed: (is (not (neg? %)))。我错过了什么?
【解决方案2】:

@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 之前被评估(并因此被抛出)可能发生。

【讨论】:

    【解决方案3】:

    类似下面的 clojure 规范解释问题的地方?这将引发您可以捕获的断言错误。

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

    【讨论】:

    • 这不是一直都是真的吗?
    • @bfontaine 不,这很好用,因为s/explain 似乎总是返回 nil(至少在 Clojure 1.10.1 中)。 但是你是对的,这个答案依赖于可能随时改变的实现细节。我建议改用 if 构造,尽管这可能有点笨拙。
    【解决方案4】:

    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) => ""
    

    编辑:启用断言检查。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多