Débogage dans Clojure? [fermé]

227

Quelles sont les meilleures façons de déboguer le code Clojure, tout en utilisant le repl?

Arun R
la source
En plus des réponses ci-dessous, voir «Outils et techniques de débogage» dans le guide REPL: clojure.org/guides/repl/…
Valentin Waeselynck

Réponses:

158

Il y a aussi dotrace, qui vous permet de regarder les entrées et sorties des fonctions sélectionnées.

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

produit la sortie:

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

Dans Clojure 1.4, dotracea déménagé:

Vous avez besoin de la dépendance:

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

Et vous devez ajouter le ^: dynamic à la définition de la fonction

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

Ensuite, Bob est à nouveau votre oncle:

(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
John Lawrence Aspden
la source
2
Bien, mais comment faire pour que clojure trouve 'clojure.contrib.trace? J'ai le pot de clojure-contrib sur mon user=> (use 'closure.contrib.trace) java.io.FileNotFoundException: Could not locate closure/contrib/trace__init.class or closure/contrib/trace.clj on classpath: (NO_SOURCE_FILE:0)
chemin de classe
2
Pourriez-vous mal orthographier clojure comme fermeture, ou est-ce une faute de frappe dans le commentaire? Pouvez-vous charger d'autres bibliothèques clojure.contrib?
John Lawrence Aspden
12
Depuis la version 1.3, cela a été déplacé vers clojure.tools.trace ( github.com/clojure/tools.trace )
George
4
Si vous obtenez: "IllegalStateException ne peut pas lier dynamiquement une variable non dynamique" voir ici: stackoverflow.com/questions/8875353/…
Cornelius
2
Cela fonctionne-t-il également dans la version 1.5? J'apprends Clojure avec les koans clojure, mais je ne peux pas encore faire fonctionner dotrace.
nha
100

J'ai une petite macro de débogage que je trouve très utile:

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

Vous pouvez l'insérer où vous voulez regarder ce qui se passe et quand:

;; 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)
John Lawrence Aspden
la source
Un peu comme clojure.tools.trace/trace.
Zaz
4
Encore mieux: Spyscope .
Zaz
@Zaz, je suis totalement d'accord. Spyscope est génial! Encore mieux peut-être qu'un débogueur. Certainement pour taper.
J Atkin
66

Le CIDER d'Emacs a un débogueur source que vous pouvez parcourir expression par expression dans un tampon Emacs et même injecter de nouvelles valeurs. Vous pouvez tout lire ici . Une capture d'écran de démonstration:

Débogage CIDER

Amumu
la source
46

Ma méthode préférée est un saupoudrage libéral de printlns partout dans le code ... Les allumer et éteindre est facile grâce à la #_macro de lecture (qui fait lire le lecteur sous la forme suivante, puis fait semblant de ne jamais l'avoir vu). Ou vous pouvez utiliser une macro se développant sur un corps transmis ou en nilfonction de la valeur d'une variable spéciale, par exemple *debug*:

(defmacro debug-do [& body]
  (when *debug*
    `(do ~@body)))

Avec un (def *debug* false)là-dedans, cela s'étendra à nil. Avec true, il s'agrandira pour bodys'envelopper dans un do.


La réponse acceptée à cette question SO: Clojure idiomatique pour les rapports d'étape? est très utile lors du débogage des opérations de séquence.


Ensuite , il y a quelque chose qui est actuellement incompatible avec Swank-clojure de » REPL, mais il est trop bon pour ne pas mentionner: debug-repl. Vous pouvez l'utiliser dans un REPL autonome, ce qui est facile à obtenir par exemple avec Leiningen ( lein repl); et si vous lancez votre programme à partir de la ligne de commande, alors il va afficher son propre REPL directement dans votre terminal. L'idée est que vous pouvez laisser tomber la debug-replmacro où vous voulez et avoir apporter son propre REPL lorsque les biefs d'exécution du programme qui points, avec toutes les sections locales de périmètre , etc. Un couple de liens pertinents: Le Clojure debug-repl , debug Clojure -repl astuces , comment faire un débogage-repl (sur le groupe Google Clojure), débogage-repl sur Clojars .


swank-clojure fait un travail adéquat pour rendre le débogueur intégré de SLIME utile lorsque vous travaillez avec du code Clojure - notez comment les bits non pertinents de la trace de pile sont grisés afin qu'il soit facile de trouver le problème réel dans le code en cours de débogage. Une chose à garder à l'esprit est que les fonctions anonymes sans «balises de nom» apparaissent dans la pile de traces sans aucune information utile qui leur est attachée; quand une "étiquette de nom" est ajoutée, elle apparaît dans le stacktrace et tout va bien à nouveau:

(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)
                   ^^^
Michał Marczyk
la source
5
En fait, il existe une version du débogage-repl qui fonctionne avec swank maintenant: hugoduncan.org/post/2010/… (Alerte spoiler : c'est génial)
1
D'accord, et c'est bien d'avoir un lien ici, merci! D'accord sur génial. :-)
Michał Marczyk
Si tel est votre style, vous aimerez peut-être la bibliothèque debux mentionnée dans une réponse ultérieure. github.com/philoskim/debux
Mallory-Erik
@ Mallory-Erik Merci, je vais vérifier!
Michał Marczyk
37

Vous pouvez également insérer du code pour vous déposer dans un REPL avec toutes les liaisons locales, en utilisant Alex Osbornedebug-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))))

Ensuite, pour l'utiliser, insérez-le où vous voulez que la réponse démarre:

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

Je le colle dans mon user.clj donc il est disponible dans toutes les sessions REPL.

thnetos
la source
16

"meilleures façons de déboguer le code Clojure, tout en utilisant la repl"

Champ légèrement à gauche, mais «en utilisant le REPL lui-même».

J'écris à l'amateur Clojure depuis plus d'un an et je n'ai pas ressenti un grand besoin d'outils de débogage. Si vous gardez vos fonctions petites et exécutez chacune avec les entrées attendues au REPL et observez les résultats, il devrait être possible d'avoir une image assez claire du comportement de votre code.

Je trouve qu'un débogueur est le plus utile pour observer STATE dans une application en cours d'exécution. Clojure facilite (et amusant!) L'écriture dans un style fonctionnel avec des structures de données immuables (pas de changement d'état). Cela réduit considérablement le besoin d'un débogueur. Une fois que je sais que tous les composants se comportent comme prévu (en accordant une attention particulière aux types de choses), le comportement à grande échelle est rarement un problème.

Peter Westmacott
la source
C'est surtout vrai, mais lorsque vous avez une récursivité entre plusieurs fonctions, par exemple, ce n'est pas si simple.
John
9

Si vous utilisez emacs / slime / swank, essayez ceci au REPL:

(defn factorial [n]
        (cond (< n 2) n
              (= n 23) (swank.core/break)
              :else (* n (factorial (dec n)))))

(factorial 30)

Cela ne vous donne pas une trace complète de la pile comme vous le feriez sous LISP, mais c'est bon pour fouiner.

C'est le beau travail de:

http://hugoduncan.org/post/2010/swank_clojure_gets_a_break_with_the_local_environment.xhtml

comme cela a été mentionné dans un commentaire ci-dessus.

John Lawrence Aspden
la source
9

Pour IntelliJ, il existe un excellent plugin Clojure appelé Cursive . Entre autres choses, il fournit un REPL que vous pouvez exécuter en mode débogage et parcourir votre code Clojure comme vous le feriez par exemple pour Java.

Je voudrais cependant seconder la réponse de Peter Westmacott dans la mesure où, d'après mon expérience, l'exécution de morceaux de mon code dans le REPL est la plupart du temps une forme suffisante de débogage.

dskrvk
la source
J'utilisais La Clojure avec succès, mais semble mourir en faveur de cursive maintenant github.com/JetBrains/la-clojure/blob/master/README.md
leeor
Mais comment déboguer avec Leiningen, cela montre:Error running 'ring server': Trampoline must be enabled for debugging
Gank
Cela semble être spécifique à - ringou leinpeut-être la peine de publier une question distincte?
dskrvk
6

Depuis 2016, vous pouvez utiliser Debux , une bibliothèque de débogage simple pour Clojure / Script qui fonctionne en conjonction avec votre repl ainsi que la console de votre navigateur. Vous pouvez saupoudrer dbg(déboguer) ou clog(console.log) des macros dans votre code et observer facilement les résultats des fonctions individuelles, etc., imprimés sur votre REPL et / ou console.

Du Readme du projet :

Utilisation basique

Ceci est un exemple simple. La macro dbg imprime un formulaire original et imprime la valeur évaluée dans la fenêtre REPL. Ensuite, il renvoie la valeur sans interférer avec l'exécution du code.

Si vous enveloppez le code avec dbg comme ceci,

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

les éléments suivants seront imprimés dans la fenêtre REPL.

Sortie REPL:

dbg: (+ 10 20) => 30

DBG imbriqué

La macro dbg peut être imbriquée.

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

Sortie REPL:

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

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

Mallory-Erik
la source
5

Hugo Duncan et ses collaborateurs continuent de faire un travail incroyable avec le projet Ritz . Ritz-nrepl est un serveur nREPL avec des capacités de débogage. Regardez les débogueurs de Hugo dans Clojure parler à Clojure / Conj 2012 pour le voir en action, dans la vidéo certaines des diapositives ne sont pas lisibles, vous voudrez peut-être voir les diapositives d' ici .

Rodrigo Taboada
la source
1

Venant de Java et étant familier avec Eclipse, j'aime ce que Counterclockwise (le plugin Eclipse pour le développement de Clojure) a à offrir: http://doc.ccw-ide.org/documentation.html#_debug_clojure_code

yotsov
la source
aucun support de point d'arrêt approprié. Les fils sont difficiles à tuer.
CodeFarmer
1

Voici une belle macro pour déboguer des letformulaires compliqués :

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

... et un essai expliquant son utilisation .

Jouni K. Seppänen
la source
-4

Version fonctionnelle de def-let, qui transforme un let en une série de defs. Un peu de crédit va ici

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

Utilisation: doit citer le contenu avec une citation, par exemple

(def-let '[a 1 b 2 c (atom 0)])
Kevin Zhu
la source