2016-06-17 15 views
14

Ich habe die letzten Tage verwendet, um tiefer in clojure.spec in Clojure und ClojureScript zu graben.Bedeutungsvolle Fehlermeldung für Clojure.Spec Validierung in: vor

Bis jetzt finde ich es am nützlichsten, Spezifikationen in :pre und :post in öffentlichen Funktionen, die auf Daten in einem bestimmten Format beruhen, als Wächter zu verwenden.

(defn person-name [person] 
    {:pre [(s/valid? ::person person)] 
    :post [(s/valid? string? %)]} 
    (str (::first-name person) " " (::last-name person))) 

Das Problem mit diesem Ansatz ist, dass ich ein java.lang.AssertionError: Assert failed: (s/valid? ::person person) ohne Informationen über bekommen, was genau die Spezifikation nicht erfüllt hat.

Hat jemand eine Idee wie eine bessere Fehlermeldung erhalten in :pre oder :post Wachen?

Ich weiß über conform und explain*, aber das hilft nicht in diesen :pre oder :post Wachen.

Antwort

5

Ich denke, die Idee ist, dass Sie spec/instrument verwenden, um Funktionseingabe und -ausgabe zu validieren, anstatt Pre-und Post-Bedingungen.

Es gibt ein gutes Beispiel zum Ende dieses Blogposts: http://gigasquidsoftware.com/blog/2016/05/29/one-fish-spec-fish/. Kurzzusammenfassung: Sie können eine Spezifikation für eine Funktion definieren, einschließlich Eingabe- und Rückgabewerte, indem Sie die Tasten args und: ret verwenden (also sowohl die Vor- als auch die Nachbedingungen ersetzen), mit spec/fdef, es instrumentieren und die Ausgabe ähnlich wie mit explain ausgeben wenn es die Spezifikation nicht erfüllt.

Minimal Beispiel aus dieser Verbindung ableiten:

(spec/fdef your-func 
    :args even? 
    :ret string?) 


(spec/instrument #'your-func) 

Und das ist äquivalent eine Voraussetzung zu bringen, dass die Funktion eine ganze Zahl Argument und eine Nachbedingung, dass er eine Zeichenkette zurückgibt. Außer Sie erhalten viel mehr nützliche Fehler, genau wie Sie es wünschen.

Weitere Details in der offiziellen Anleitung: https://clojure.org/guides/spec --- siehe unter der Überschrift "Spezifikationsfunktionen".

+1

Klingt vernünftig. Werde das versuchen. – DiegoFrings

+3

Dies wird jedoch keine Rückgabewerte überprüfen, daher ersetzt das Instrument nur: pre. Die Idee ist, dass Sie test.check verwenden, um zu testen, ob Ihre Funktionen die Eingabe korrekt ausgeben. Dann instrumentieren Sie und testen Sie Ihre Funktionen in der Integration durch normale Tests. Schließlich verwenden Sie s/assert wo immer Sie wollen, Produktionswächter, wie in Alex Miller's Antwort erwähnt. –

+0

: ret-Werte können auch mit Orchester instrumentiert werden. https://github.com/jeaye/orchestra – xtreak

1

Ohne Berücksichtigung, wenn Sie vor und nach Bedingungen verwenden sollten Funktionsargumente zu bestätigen, ist es eine Möglichkeit, etwas klare Nachrichten von Pre- und Post-Bedingungen zu drucken, indem Sie Ihr Prädikat mit clojure.test/is Einwickeln, wie unten in der Antwort vorgeschlagen:

How can I get Clojure :pre & :post to report their failing value?

Also dann könnte Ihr Code wie folgt aussehen:

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

Auswertung

pre-post-messages.core> (person-name try-2) 

produzieren würde

"Anna Vissi" 

und Bewertung

pre-post-messages.core> (person-name try-1) 

+0

Ich bin nicht sicher über den Test 'clojure.test' Namespace im Produktionscode. Aber das scheint eine praktikable Option zu sein, um aussagekräftige Nachrichten von ': pre' Wächtern zu bekommen. – DiegoFrings

7

In neueren Alphas produzieren würde, gibt es jetzt s/assert, die verwendet werden kann, dass ein Eingang zu behaupten oder Rückgabewert entspricht einer Spezifikation. Falls gültig, wird der ursprüngliche Wert zurückgegeben. Wenn ungültig, wird ein Assertion-Fehler mit dem EXPLAIN-Ergebnis ausgelöst. Behauptungen können ein- oder ausgeschaltet werden und können sogar optional aus dem kompilierten Code weggelassen werden, um eine Auswirkung auf die Produktion zu haben.

(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} 
+0

Es wäre schön gewesen, s/assert oder s/explain * im pre: und: post "hooks" zu verwenden ... –

+0

Sie können sie in pre und post benutzen? –

+0

Nichts passiert, wenn ich dieses (scheiternde) s/assert in '(defn f [x] {: pre [(s/assert string? X)]} (println x))' Aber s/explain erfasse es so, dass ist in Ordnung für mich. (1.9.0-alpha13) –