2016-01-21 14 views
5

Attualmente quando sto refactoring in Clojure Io tendo ad usare il modello:Esiste un buon metodo per verificare i tipi di reso durante il refactoring?

(defn my-func [arg1 arg2] 
    (assert (= demo.core.Record1 (class arg1) "Incorrect class for arg1: " (class arg1)) 
    (assert (= demo.core.Record1 (class arg2) "Incorrect class for arg2: " (class arg2)) 
    ... 

Cioè, mi ritrovo controllare manualmente i tipi restituiti nel caso in cui una parte a valle del sistema li modifica a qualcosa I don' aspettarsi. (Come in, se mi rifatto e ottengo una traccia di stack che non mi aspetto, allora esprimo le mie ipotesi come invarianti e passo avanti da lì).

In un certo senso, questo è esattamente il tipo di controllo invariante previsto per Bertrand Meyer. (Autore di Object Oriented Software Construction e proponente dell'idea di Design by Contract).

La sfida è che non li trovo fino al momento dell'esecuzione. Sarebbe bello trovarli al momento della compilazione, semplicemente dichiarando cosa si aspetta la funzione.

Ora Conosco Clojure è essenzialmente un linguaggio dinamico. (Mentre Clojure ha un tipo di "compilatore", dovremmo aspettarci che l'applicazione dei valori a una funzione giunga alla realizzazione solo in fase di esecuzione.)

Voglio solo un buon modello per semplificare il refactoring. (Vale a dire vedere tutti gli effetti di flusso di una discussione su una funzione, senza vederla interrompere la prima chiamata, quindi passare alla successiva, quindi passare alla successiva interruzione.)

La mia domanda è: È c'è un buon modo per controllare i tipi di ritorno durante il refactoring?

risposta

2

di avere un paio di opzioni, una sola delle quali è "compilazione" tempo:

Test

Come Clojure è un linguaggio dinamico, i test sono assolutamente essenziali. Sono la tua rete di sicurezza durante il refactoring. Anche nei linguaggi con tipizzazione statica i test sono ancora utili.

pre e post condizioni

Essi consentono di verificare le invarianti con l'aggiunta di metadati per le funzioni, come in questo esempio da Michael Fogus' blog:

(defn constrained-fn [f x] 
    {:pre [(pos? x)] 
    :post [(= % (* 2 x))]} 
    (f x)) 

(constrained-fn #(* 2 %) 2) 
;=> 4 
(constrained-fn #(float (* 2 %)) 2) 
;=> 4.0 
(constrained-fn #(* 3 %) 2) 
;=> java.lang.Exception: Assert failed: (= % (* 2 x) 

core.typed

core.typed è l'unica opzione in questo elenco che ti darà il controllo del tempo di compilazione.Il tuo esempio potrebbe quindi essere espresso in questo modo:

(ann my-func (Fn [Record1 Record1 -> ResultType])) 
(defn my-func [arg1 arg2] 
...) 

Ciò va a scapito di correre core.typed come azione separata, forse come parte della vostra suite di test.

E ancora sul runtime di validazione/controllo, ci sono ancora più opzioni come bouncer e schema.

3

Se ho capito bene, prismatico/schema dovrebbe essere la vostra scelta. https://github.com/plumatic/schema

(s/defn ^:always-validate my-func :- SomeResultClass 
    [arg1 :- demo.core.Record1 
    arg2 :- demo.core.Record1] 
    ...) 

si deve solo disattivare tutte la convalida prima del rilascio, in modo da non influire sulle prestazioni.

core.typed è bello, ma per quanto mi ricordo, impone di annotare tutto il codice, mentre lo schema consente solo di annotare parti critiche.