2010-02-28 18 views
207

Quali sono i modi migliori per eseguire il debug del codice Clojure, mentre si utilizza il repl?Debug in Clojure?

risposta

43

Il mio metodo preferito è una spolverata abbondante di println tutto finito il codice ... loro Accensione e spegnimento è facile grazie alla macro #_ lettore (che rende il lettore di leggere il seguente modulo, poi finta che sia mai visto). Oppure si potrebbe utilizzare una macro in espansione sia a un passato-in del corpo o nil a seconda del valore di qualche variabile speciale, dicono *debug*:

(defmacro debug-do [& body] 
    (when *debug* 
    `(do [email protected]))) 

Con un (def *debug* false) in là, questo si espanderà a nil. Con true, si espanderà a body avvolto in un do.


La risposta accettata a questa domanda SO: Idiomatic Clojure for progress reporting? è molto utile durante il debug operazioni di sequenza.


Poi c'è qualcosa che è attualmente incompatibile con REPL Swank-clojure 's, ma è troppo bello per non parlare: debug-repl. Puoi usarlo in un REPL standalone, che è facile da ottenere ad es. con Leiningen (lein repl); e se stai lanciando il tuo programma dalla riga di comando, allora porterà il proprio REPL direttamente nel tuo terminale. L'idea è che puoi rilasciare la macro debug-repl ovunque desideri e farla aprire il REPL proprio quando l'esecuzione del programma raggiunge quel punto, con tutti i locali nello scope ecc. Un paio di link rilevanti: The Clojure debug-repl, Clojure debug-repl tricks, su how 'bout a debug-repl il gruppo Clojure di Google), debug-repl on Clojars.


Swank-clojure fa un lavoro adeguato di rendere debugger integrato di SLIME utile quando si lavora con il codice Clojure - notare come i bit irrilevanti del stacktrace vengono visualizzati in grigio in modo che sia facile trovare il problema reale nella codice in fase di debug. Una cosa da tenere a mente è che le funzioni anonime senza "tag name" appaiono nello stacktrace senza fondamentalmente nessuna informazione utile ad esse connessa; quando viene aggiunto un "tag nome", in quanto sembra nel stacktrace e tutto è di nuovo bene:

(fn [& args] ...) 
vs. 
(fn tag [& args] ...) 

example stacktrace entries: 
1: user$eval__3130$fn__3131.invoke(NO_SOURCE_FILE:1) 
vs.    ^^ 
1: user$eval__3138$tag__3139.invoke(NO_SOURCE_FILE:1) 
        ^^^ 
+5

In realtà c'è una versione di debug-repl che funziona con Swank ora: http://hugoduncan.org/post/ 2010/swank_clojure_gets_a_break_with_the_local_environment.xhtml (Spoiler alert: è impressionante) – technomancy

+1

destro, ed è bene avere un collegamento qui, grazie! D'accordo sul fantastico. :-) –

+0

Se questo è il tuo stile, potresti gradire la libreria di debux menzionata in una risposta successiva. https://github.com/philoskim/debux –

97

ho un po 'di macro di debug che trovo molto utile:

;;debugging parts of expressions 
(defmacro dbg[x] `(let [x# ~x] (println "dbg:" '~x "=" x#) x#)) 

È possibile inserire dove si vuole guardare cosa sta succedendo e quando:

;; Examples of dbg 
(println (+ (* 2 3) (dbg (* 8 9)))) 
(println (dbg (println "yo"))) 
(defn factorial[n] (if (= n 0) 1 (* n (dbg (factorial (dec n)))))) 
(factorial 8) 

(def integers (iterate inc 0)) 
(def squares (map #(dbg(* % %)) integers)) 
(def cubes (map #(dbg(* %1 %2)) integers squares)) 
(take 5 cubes) 
(take 5 cubes) 
+0

Un po 'come [ 'clojure.tools.trace/trace'] (https://github.com/clojure/tools.trace). – Zaz

+4

Ancora meglio: [Spyscope] (https://github.com/dgrnbrg/spyscope). – Zaz

+0

@Zaz Sono totalmente d'accordo. Spyscope è fantastico! Ancora meglio forse di un debugger. Sicuramente per la digitazione. –

139

C'è anche dotrace, che permette di guardare gli ingressi e le uscite delle funzioni selezionate.

(use 'clojure.contrib.trace) 
(defn fib[n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) 
(dotrace [fib] (fib 3)) 

produce l'uscita:

TRACE t4425: (fib 3) 
TRACE t4426: | (fib 2) 
TRACE t4427: | | (fib 1) 
TRACE t4427: | | => 1 
TRACE t4428: | | (fib 0) 
TRACE t4428: | | => 0 
TRACE t4426: | => 1 
TRACE t4429: | (fib 1) 
TRACE t4429: | => 1 
TRACE t4425: => 2 
2 

In Clojure 1.4, dotrace si è spostato:

È necessario la dipendenza:

[org.clojure/tools.trace "0.7.9"] 
(require 'clojure.tools.trace) 

ed è necessario aggiungere il ^: dinamico alla definizione di funzione

(defn ^:dynamic fib[n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) 

Poi Bob è ancora una volta tuo zio:

(clojure.tools.trace/dotrace [fib] (fib 3)) 

TRACE t4328: (fib 3) 
TRACE t4329: | (fib 2) 
TRACE t4330: | | (fib 1) 
TRACE t4330: | | => 1 
TRACE t4331: | | (fib 0) 
TRACE t4331: | | => 0 
TRACE t4329: | => 1 
TRACE t4332: | (fib 1) 
TRACE t4332: | => 1 
TRACE t4328: => 2 
+2

Nizza, ma come si fa a trovare clojure 'clojure.contrib.trace? Ho il jar clojure-contrib sul mio classpath ma REPL dice 'user => (usa 'closure.contrib.trace) java.io.FileNotFoundException: Impossibile trovare la chiusura/contrib/trace__init.class o closure/contrib/trace. CLJ sul classpath: (NO_SOURCE_FILE: 0) ' – LarsH

+2

Potrebbe essere errore ortografico clojure come la chiusura, o è un errore di battitura nel commento? Puoi caricare altre librerie clojure.contrib? –

+12

A partire da 1.3 questo è stato spostato in clojure.tools.trace (https://github.com/clojure/tools.trace) – George

35

È anche possibile inserire codice per inserirsi in un REPL con tutto il locale legature, utilizzando Alex Osborne's debug-repl:

(defmacro local-bindings 
    "Produces a map of the names of local bindings to their values." 
    [] 
    (let [symbols (map key @clojure.lang.Compiler/LOCAL_ENV)] 
    (zipmap (map (fn [sym] `(quote ~sym)) symbols) symbols))) 

(declare *locals*) 
(defn eval-with-locals 
    "Evals a form with given locals. The locals should be a map of symbols to 
values." 
    [locals form] 
    (binding [*locals* locals] 
    (eval 
    `(let ~(vec (mapcat #(list % `(*locals* '~%)) (keys locals))) 
     ~form)))) 

(defmacro debug-repl 
    "Starts a REPL with the local bindings available." 
    [] 
    `(clojure.main/repl 
    :prompt #(print "dr => ") 
    :eval (partial eval-with-locals (local-bindings)))) 

Poi Per utilizzarlo, inserire ovunque si desidera che il repl per iniziare:

(defn my-function [a b c] 
    (let [d (some-calc)] 
    (debug-repl))) 

mi attengo nel mio user.clj modo che sia disponibile in tutte le sessioni REPL.

2

Here's una bella macro per il debug complessi let forme:

(defmacro def+ 
    "def with binding (def+ [{:keys [a b d]} {:a 1 :b 2 :d 3}])" 
    [bindings] 
    (let [let-expr (macroexpand `(let ~bindings)) 
     vars (filter #(not (.contains (str %) "__")) 
       (map first (partition 2 (second let-expr)))) 
     def-vars (map (fn [v] `(def ~v ~v)) vars)] 
    (concat let-expr def-vars))) 

... e an essay explaining its use.

-4

Versione funzione di def-let, che trasforma una let in una serie di errori. Alcuni merito va a here

(defn def-let [aVec] 
    (if-not (even? (count aVec)) 
    aVec 
    (let [aKey (atom "")  
      counter (atom 0)] 
     (doseq [item aVec] 
     (if (even? @counter) 
      (reset! aKey item)   
      (intern *ns* (symbol @aKey) (eval item))) 
     ; (prn item)  
    (swap! counter inc))))) 

Usage: ha bisogno di citare il contenuto con una citazione, per esempio

(def-let '[a 1 b 2 c (atom 0)]) 
5

Hugo Duncan e collaboratori continuano a fare il lavoro incredibile con il progetto ritz. Ritz-nrepl è un server nREPL con funzionalità di debug. Guarda il video di Hugo Debuggers in Clojure in Clojure/Conj 2012 per vederlo in azione, nel video alcune diapositive non sono leggibili quindi potresti voler visualizzare le diapositive da here.

12

"modi migliori per il codice di debug Clojure, durante l'utilizzo del repl"

Leggermente sinistra-campo, ma 'con il REPL iteself'.

Scrivo l'hobbista Clojure da oltre un anno e non ho sentito un grande bisogno di strumenti di debug. Se mantieni le tue funzioni ridotte e esegui ciascuna di esse con gli input previsti al REPL e osservi i risultati, dovrebbe essere possibile avere un'immagine chiara di come si sta comportando il tuo codice.

Trovo che un debugger sia più utile per osservare STATE in un'applicazione in esecuzione. Clojure rende facile (e divertente!) Scrivere in uno stile funzionale con strutture di dati immutabili (nessuno stato mutevole). Questo riduce enormemente la necessità di un debugger. Una volta che so che tutti i componenti si comportano come mi aspetto (prestando particolare attenzione ai tipi di cose), il comportamento su larga scala è raramente un problema.

+0

Questo è principalmente vero, ma quando si esegue la ricorsione su più funzioni, ad esempio, non è così facile. – John

52

Il CIDER di Emacs ha un debugger di origine che consente di inserire espressioni per espressione all'interno di un buffer Emacs e persino di iniettare nuovi valori. Puoi leggere tutto su di esso here. Una demo screenshot:

CIDER debug

6

per IntelliJ c'è un ottimo plug-in Clojure chiamato Cursive. Tra le altre cose, fornisce un REPL che puoi eseguire in modalità di debug e scorrere il codice Clojure proprio come faresti per es. Giava.

Vorrei in secondo luogo la risposta di Peter Westmacott in quanto nella mia esperienza l'esecuzione di parti del mio codice nel REPL è quasi sempre una forma di debug sufficiente.

+0

Stavo usando La Clojure con successo, ma sembra che stia morendo a favore di cursive ora https://github.com/JetBrains/la-clojure/blob/master/README.md – leeor

6

A partire dal 2016 è possibile utilizzare Debux, una semplice libreria di debug per Clojure/Script che funziona in combinazione con il proprio repl e la console del browser. È possibile cospargere i codici dbg (debug) o clog (console.log) nel codice e osservare facilmente i risultati delle singole funzioni, ecc., Stampati sul REPL e/o sulla console.

Dal Readme del progetto:

Utilizzo base

Questo è un semplice esempio. La macro dbg stampa un modulo originale e pretty-prints il valore valutato nella finestra REPL. Quindi restituisce il valore senza interferire con l'esecuzione del codice.

Se si avvolge il codice con dbg come questo,

(* 2 (dbg (+ 10 20))) ; => 60

seguito verrà stampata nella finestra REPL.

uscita REPL:

dbg: (+ 10 20) => 30

nidificati DBG

La macro DBG possono essere nidificate.Uscita

(dbg (* 2 (dbg (+ 10 20)))) ; => 60

REPL:

`dbg: (+ 10 20) => 30` 

dbg: (* 2 (dbg (+ 10 20))) => 60