Значительное сообщение об ошибке для Clojure. Проверка проверки в: pre
Я использовал последние дни, чтобы углубиться в clojure.spec в Clojure и ClojureScript.
До сих пор я нахожу это наиболее полезным, использовать спецификации в качестве охранников в :pre
и :post
в публичных функциях, которые полагаются на данные в определенном формате.
(defn person-name [person]
{:pre [(s/valid? ::person person)]
:post [(s/valid? string? %)]}
(str (::first-name person) " " (::last-name person)))
Проблема с этим подходом заключается в том, что я получаю java.lang.AssertionError: Assert failed: (s/valid? ::person person)
без какой-либо информации о , что точно не соответствовало спецификации.
Есть ли у кого-нибудь идея как получить лучшее сообщение об ошибке в :pre
или :post
охранниках?
Я знаю о conform
и explain*
, но это не помогает тем сторонам :pre
или :post
.
Ответы
Ответ 1
В новых альфах теперь есть s/assert
, который может использоваться для утверждения, что входное или возвращаемое значение соответствует спецификации. Если оно действительное, возвращается исходное значение. Если это неверно, с результатом объяснения возникает ошибка утверждения. Утверждения могут быть включены или выключены и могут быть необязательно исключены из скомпилированного кода целиком, чтобы иметь влияние на производство 0.
(s/def ::first-name string?)
(s/def ::last-name string?)
(s/def ::person (s/keys :req [::first-name ::last-name]))
(defn person-name [person]
(s/assert ::person person)
(s/assert string? (str (::first-name person) " " (::last-name person))))
(s/check-asserts true)
(person-name 10)
=> CompilerException clojure.lang.ExceptionInfo: Spec assertion failed
val: 10 fails predicate: map?
:clojure.spec/failure :assertion-failed
#:clojure.spec{:problems [{:path [], :pred map?, :val 10, :via [], :in []}], :failure :assertion-failed}
Ответ 2
Я думаю, что идея заключается в том, что вы используете spec/instrument
для проверки ввода и вывода функций, а не условий предварительной и последующей.
Вот хороший пример в нижней части этого сообщения в блоге: http://gigasquidsoftware.com/blog/2016/05/29/one-fish-spec-fish/. Краткое описание: вы можете определить спецификацию для функции, включая как входные, так и возвращаемые значения, используя клавиши: args и: ret (таким образом, заменяя как условия pre, так и post), с помощью spec/fdef
, измерить его, и вы получите результат, аналогичный используя explain
, когда он не отвечает спецификации.
Минимальный пример, полученный из этой ссылки:
(spec/fdef your-func
:args even?
:ret string?)
(spec/instrument #'your-func)
И это эквивалентно предположению, что функция имеет целочисленный аргумент и постусловие, что оно возвращает строку. Кроме того, вы получаете гораздо более полезные ошибки, точно так же, как вы ищите.
Подробнее в официальном руководстве: https://clojure.org/guides/spec --- см. раздел "Функции функции".
Ответ 3
Без учета того, следует ли использовать предварительные и пост-условия для проверки аргументов функции, есть способ распечатать несколько более четкие сообщения из условий pre и post, завернув ваш предикат с помощью clojure.test/is
, как это предлагается в ответе ниже:
Как я могу получить Clojure: pre и: post, чтобы сообщить о своем неудачном значении?
Итак, ваш код может выглядеть так:
(ns pre-post-messages.core
(:require [clojure.spec :as s]
[clojure.test :as t]))
(defn person-name [person]
{:pre [(t/is (s/valid? ::person person))]
:post [(t/is (s/valid? string? %))]}
(str (::first-name person) " " (::last-name person)))
(def try-1
{:first-name "Anna Vissi"})
(def try-2
{::first-name "Anna"
::last-name "Vissi"
::email "[email protected]"})
(s/def ::person (s/keys :req [::first-name ::last-name ::email]))
Оценка
pre-post-messages.core> (person-name try-2)
создаст
"Anna Vissi"
и оценивая
pre-post-messages.core> (person-name try-1)
создаст
FAIL in () (core.clj:6)
expected: (s/valid? :pre-post-messages.core/person person)
actual: (not (s/valid? :pre-post-messages.core/person {:first-name "Anna Vissi"}))
AssertionError Assert failed: (t/is (s/valid? :pre-post-messages.core/person person)) pre-post-messages.core/person-name (core.clj:5)