debugging clojure

debugging - ¿Depuración en Clojure?



(14)

"las mejores formas de depurar el código de Clojure, mientras se usa la respuesta"

Un poco de campo izquierdo, pero ''usando el REPL por sí mismo''.

He estado escribiendo Clojure aficionado durante más de un año y no he sentido una gran necesidad de herramientas de depuración. Si mantiene sus funciones pequeñas y ejecuta cada una con las entradas esperadas en el REPL y observa los resultados, entonces debería ser posible tener una idea bastante clara de cómo se está comportando su código.

Me parece que un depurador es más útil para observar STATE en una aplicación en ejecución. Clojure hace que sea fácil (y divertido) escribir en un estilo funcional con estructuras de datos inmutables (sin cambio de estado). Esto reduce masivamente la necesidad de un depurador. Una vez que sé que todos los componentes se comportan como lo espero (prestando especial atención a los tipos de cosas), el comportamiento a gran escala rara vez es un problema.

¿Cuáles son las mejores maneras de depurar el código de Clojure, al usar la respuesta?


A partir de 2016, puede usar github.com/philoskim/debux , una biblioteca de depuración simple para Clojure / Script que funciona junto con su respuesta y la consola de su navegador. Puede esparcir macros dbg (depuración) u clog (console.log) en su código y observar fácilmente los resultados de funciones individuales, etc., impresas en su REPL y / o consola.

Desde el github.com/philoskim/debux del proyecto:

Uso básico

Este es un ejemplo simple. La macro dbg imprime un formulario original y imprime de forma bonita el valor evaluado en la ventana REPL. Luego devuelve el valor sin interferir con la ejecución del código.

Si envuelves el código con dbg como este,

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

Lo siguiente se imprimirá en la ventana REPL.

Salida REPL:

dbg: (+ 10 20) => 30

Dbg anidado

La macro dbg puede estar anidada.

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

Salida REPL:

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

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



El CIDER de Emacs obtuvo un depurador de origen que puede pasar expresión por expresión dentro de un búfer de Emacs e incluso inyectar nuevos valores. Puedes leer todo sobre esto here . Una captura de pantalla de demostración:


Hugo Duncan y sus colaboradores continúan haciendo un trabajo increíble con el proyecto ritz . Ritz-nrepl es un servidor nREPL con capacidades de depuración. Mire a los Debuggers de Hugo en Clojure hablar en Clojure / Conj 2012 para verlo en acción, en el video algunas de las diapositivas no se pueden leer, por lo que es posible que desee ver las diapositivas desde here .


Mi método favorito es una gran cantidad de println todo el código ... println y desactivarlas es fácil gracias a la macro #_ reader (que hace que el lector lea el siguiente formulario y luego finja que nunca lo ha visto). O puede usar una macro que se expanda a un cuerpo pasado o nil dependiendo del valor de alguna variable especial, digamos *debug* :

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

Con un (def *debug* false) ahí, esto se expandirá a nil . Con true , se expandirá al body envuelto en un do .

La respuesta aceptada a esta pregunta de SO: ¿ Clojure idiomático para informes de progreso? Es muy útil al depurar operaciones de secuencia.

Luego hay algo que actualmente es incompatible con el REPL de swank-clojure , pero es demasiado bueno para no mencionarlo: debug-repl . Puede usarlo en un REPL autónomo, que es fácil de obtener, por ejemplo, con Leiningen ( lein repl ); y si está iniciando su programa desde la línea de comandos, entonces su propio REPL se activará en su terminal. La idea es que puede colocar la macro debug-repl en cualquier lugar que desee y hacer que muestre su propio REPL cuando la ejecución del programa llegue a ese punto, con todos los locales en el ámbito, etc. Un par de enlaces relevantes: The Clojure debug-repl , Trucos de debug-repl de Clojure , cómo sobre una debug-repl (en el grupo de Google Clojure), debug-repl en Clojars .

swank-clojure hace un trabajo adecuado para hacer que el depurador incorporado de SLIME sea útil cuando se trabaja con el código de Clojure. Observe cómo los bits irrelevantes del seguimiento de la pila están en gris, por lo que es fácil encontrar el problema real en el código que se está depurando. Una cosa que se debe tener en cuenta es que las funciones anónimas sin "etiquetas de nombre" aparecen en el seguimiento de pila y, básicamente, no tienen información útil adjunta; cuando se agrega una "etiqueta de nombre", aparece en el seguimiento de pila y todo vuelve a estar bien:

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


Para IntelliJ hay un excelente complemento de Clojure llamado Cursive . Entre otras cosas, proporciona un REPL que puede ejecutar en modo de depuración y paso a través de su código Clojure como lo haría para, por ejemplo, Java.

Sin embargo, me gustaría respaldar la respuesta de Peter Westmacott, ya que, en mi experiencia, solo ejecutar partes de mi código en el REPL es la mayoría de las veces una forma suficiente de depuración.


Si usa emacs / slime / swank, intente esto en el REPL:

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

No te da un seguimiento completo de la pila como lo harías con LISP, pero es bueno para hurgarlo.

Este es el buen trabajo de:

hugoduncan.org/post/2010/…

como se mencionó en un comentario anterior.


También hay dotrace, que le permite ver las entradas y salidas de las funciones seleccionadas.

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

produce la salida:

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

En Clojure 1.4, dotrace ha movido:

Necesitas la dependencia:

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

Y necesitas agregar el ^: dynamic a la definición de la función

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

Entonces Bob vuelve a ser tu tío:

(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


También puede insertar código para caer en un REPL con todos los enlaces locales, utilizando la debug-repl Alex Osborne :

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

Luego, para usarlo, insértelo donde desee que empiece la respuesta:

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

Guardo esto en mi user.clj para que esté disponible en todas las sesiones de REPL.


Tengo una pequeña macro de depuración que me parece muy útil:

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

Puedes insertarlo donde quieras para ver qué está pasando y cuándo:

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


Utilice spyscope que implementa una macro de lector personalizado para que su código de depuración también sea un código de producción Spyscope


Versión de la función de def-let, que convierte un let en una serie de defs. Algún crédito va hasta 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)))))

Uso: Necesita cotizar el contenido con una cotización, por ej.

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


Here''s una buena macro para la depuración de formularios complicados de let :

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

... y un ensayo explicando su uso .