Debugowanie w Clojure? [Zamknięte]

227

Jakie są najlepsze sposoby debugowania kodu Clojure podczas korzystania z repl?

Arun R.
źródło
W uzupełnieniu do poniższych odpowiedzi zobacz „Narzędzia i techniki debugowania” w przewodniku REPL: clojure.org/guides/repl/…
Valentin Waeselynck

Odpowiedzi:

158

Istnieje również dotrace, który pozwala spojrzeć na wejścia i wyjścia wybranych funkcji.

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

daje wynik:

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

W Clojure 1.4 dotraceprzeprowadził się:

Potrzebujesz zależności:

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

I musisz dodać dynamikę ^: do definicji funkcji

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

Zatem Bob po raz kolejny jest wujem:

(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
źródło
2
Fajnie, ale jak zdobyć clojure, aby znaleźć „clojure.contrib.trace? Mam słoik z dodatkiem clojure na mojej ścieżce klasy, ale REPL mówiuser=> (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)
LarsH
2
Czy mógłbyś być błędnym pisaniem clojure jako zamknięcie, czy jest to literówka w komentarzu? Czy możesz załadować inne biblioteki clojure.contrib?
John Lawrence Aspden,
12
Od 1.3 przeniesiono to do clojure.tools.trace ( github.com/clojure/tools.trace )
George
4
Jeśli otrzymujesz: „IllegalStateException nie może dynamicznie powiązać nie-dynamicznego var”, zobacz tutaj: stackoverflow.com/questions/8875353/…
Cornelius
2
Czy działa również w wersji 1.5? Uczę się Clojure z koanami Clojure, ale nie mogę jeszcze dostać dotrace do pracy.
nha
100

Mam małe makro debugujące, które uważam za bardzo przydatne:

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

Możesz wstawić go tam, gdzie chcesz oglądać, co się dzieje i kiedy:

;; 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
źródło
Bardzo lubię clojure.tools.trace/trace.
Zaz
4
Jeszcze lepiej: Spyscope .
Zaz
@Zaz Całkowicie się zgadzam. Spyscope jest niesamowity! Może nawet lepszy niż debugger. Z pewnością do pisania.
J Atkin
66

CIDER Emacsa ma źródłowy debugger, w którym można krok po kroku wyrażać wyrażenie w buforze Emacsa, a nawet wprowadzać nowe wartości. Możesz przeczytać o tym wszystko tutaj . Zrzut ekranu pokazowy:

Debugowanie CIDER

Amumu
źródło
46

Moją ulubioną metodą jest swobodne posypywanie printlns całego kodu ... Włączanie i wyłączanie ich jest łatwe dzięki #_makro czytelnika (które sprawia, że ​​czytnik czyta w następującej formie, a potem udaje, że go nigdy nie widział). Możesz też użyć makra rozwijającego się do przekazywanej treści lub w nilzależności od wartości jakiejś specjalnej zmiennej, powiedz *debug*:

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

Z (def *debug* false)tam, to rozwinie się do nil. Z true, rozwinie się do bodyowiniętego w do.


Przyjęta odpowiedź na to pytanie SO: Idiomatic Clojure do zgłaszania postępów? jest bardzo pomocny podczas debugowania operacji sekwencji.


Potem jest coś, co jest obecnie niezgodne z Swank-Clojure „s REPL, ale jest zbyt dobre, nie wspominając: debug-repl. Możesz go używać w samodzielnej REPL, którą łatwo uzyskać np. Za pomocą Leiningen ( lein repl); a jeśli uruchamiasz swój program z wiersza poleceń, to uruchomi on swój REPL bezpośrednio w twoim terminalu. Chodzi o to, że można upuścić debug-replmakra w dowolnym miejscu i mieć go wychować własne rEPL gdy osiągnie wykonawcze zawarte w programie, które wskazują, ze wszystkich mieszkańców w zakresie itd. Kilka istotnych linków: Clojure debug-rEPL , Clojure debug -repl sztuczki , jak o debug-repl (w grupie Clojure Google), debug-repl w Clojars .


swank-clojure wykonuje odpowiednią pracę, czyniąc wbudowany debugger SLIME użytecznym podczas pracy z kodem Clojure - zwróć uwagę na to, jak niepotrzebne bity stacktrace są wyszarzone, więc łatwo jest znaleźć rzeczywisty problem w debugowanym kodzie. Należy pamiętać, że anonimowe funkcje bez „znaczników nazw” pojawiają się w stacktrace, w zasadzie bez żadnych przydatnych informacji; po dodaniu „plakietki” pojawia się w pliku śledzenia i wszystko jest w porządku:

(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
źródło
5
W rzeczywistości istnieje wersja debugowania-repl, która współpracuje teraz z swank : hugoduncan.org/post/2010/… ( Ostrzeżenie spoilera : jest niesamowite)
1
Tak, i dobrze jest mieć tutaj link, dzięki! Zgodził się na niesamowite. :-)
Michał Marczyk
Jeśli taki jest twój styl, może ci się spodobać biblioteka debux wymieniona w następnej odpowiedzi. github.com/philoskim/debux
Mallory-Erik
@ Mallory-Erik Dzięki, sprawdzę to!
Michał Marczyk 19.04.16
37

Możesz także wstawić kod, aby przejść do REPL ze wszystkimi lokalnymi powiązaniami, używając Alexa Osborne'adebug-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))))

Następnie, aby go użyć, wstaw go tam, gdzie chcesz rozpocząć replikę:

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

Wrzucam to do pliku user.clj, aby był dostępny we wszystkich sesjach REPL.

thnetos
źródło
16

„najlepsze sposoby debugowania kodu Clojure podczas korzystania z repl”

Nieznacznie lewe pole, ale „sam korzystam z REPL”.

Od ponad roku piszę hobbystkę Clojure i nie odczuwałem potrzeby stosowania żadnych narzędzi do debugowania. Jeśli utrzymujesz swoje funkcje na małym poziomie i uruchamiasz każdą z oczekiwanymi danymi wejściowymi na REPL i obserwujesz wyniki, powinien istnieć całkiem jasny obraz tego, jak zachowuje się twój kod.

Uważam, że debuger jest najbardziej przydatny do obserwowania stanu w uruchomionej aplikacji. Clojure ułatwia (i sprawia przyjemność!) Pisanie w funkcjonalnym stylu z niezmiennymi strukturami danych (bez zmieniającego się stanu). To znacznie zmniejsza potrzebę debuggera. Kiedy już wiem, że wszystkie komponenty zachowują się tak, jak się spodziewam (zwracając szczególną uwagę na rodzaje rzeczy), zachowanie na dużą skalę rzadko stanowi problem.

Peter Westmacott
źródło
Jest to głównie prawda, ale na przykład w przypadku rekurencji wielu funkcji nie jest to takie proste.
Jan
9

Dla IntelliJ istnieje doskonała wtyczka Clojure o nazwie Cursive . Zapewnia między innymi REPL, który można uruchomić w trybie debugowania i przechodzić przez kod Clojure, tak jak na przykład w Javie.

Poparłbym odpowiedź Petera Westmacotta, chociaż z mojego doświadczenia wynika, że ​​po prostu uruchamianie fragmentów mojego kodu w REPL jest przez większość czasu wystarczającą formą debugowania.

dskrvk
źródło
Używałem
leeor
Ale jak debugować Leiningen, pokazuje:Error running 'ring server': Trampoline must be enabled for debugging
Gank
Wydaje się, że jest to specyficzne ringlub lein- może warto zadać osobne pytanie?
dskrvk
6

Od 2016 roku możesz używać Debux , prostej biblioteki debugowania dla Clojure / Script, która działa w połączeniu z twoją repliką, a także konsolą przeglądarki. Możesz posypywać dbg(debugować) lub clog(console.log) makra w swoim kodzie i łatwo obserwować wyniki poszczególnych funkcji itp. Wydrukowane na REPL i / lub konsoli.

Z pliku Readme projektu :

Podstawowe użycie

To jest prosty przykład. Makro dbg drukuje oryginalny formularz i ładnie drukuje oszacowaną wartość w oknie REPL. Następnie zwraca wartość bez zakłócania wykonywania kodu.

Jeśli otoczysz kod dbg w ten sposób,

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

następujące elementy zostaną wydrukowane w oknie REPL.

Wyjście REPL:

dbg: (+ 10 20) => 30

Zagnieżdżona dbg

Makro dbg można zagnieżdżać.

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

Wyjście REPL:

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

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

Mallory-Erik
źródło
5

Hugo Duncan i współpracownicy nadal wykonują niesamowitą pracę z projektem Ritz . Ritz-nrepl to serwer nREPL z funkcjami debugowania. Obejrzyj debugowanie Hugo w debiucie w Clojure na Clojure / Conj 2012, aby zobaczyć, jak działa, w filmie niektóre slajdy nie są czytelne, więc możesz chcieć je obejrzeć tutaj .

Rodrigo Taboada
źródło
1

Pochodząc z Javy i znając Eclipse, podoba mi się to, co ma do zaoferowania Counterclockwise (wtyczka Eclipse do programowania Clojure): http://doc.ccw-ide.org/documentation.html#_debug_clojure_code

yotsov
źródło
brak odpowiedniego wsparcia dla punktu przerwania. Wątek jest trudny do zabicia.
CodeFarmer
1

Oto ładne makro do debugowania skomplikowanych letformularzy:

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

... i esej wyjaśniający jego użycie .

Jouni K. Seppänen
źródło
-4

Wersja funkcji def-let, która zmienia let w serię def. Trochę tutaj zasługuje

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

Zastosowanie: Wymaga cytowania treści z cytatem, np

(def-let '[a 1 b 2 c (atom 0)])
Kevin Zhu
źródło