Gỡ lỗi trong Clojure? [đóng cửa]


227

Cách tốt nhất để gỡ lỗi mã Clojure, trong khi sử dụng thay thế là gì?


Ngoài các câu trả lời bên dưới, hãy xem 'Công cụ và kỹ thuật gỡ lỗi' trong hướng dẫn REPL: clojure.org/guides/repl/
mẹo

Câu trả lời:


158

Ngoài ra còn có dotrace, cho phép bạn xem xét đầu vào và đầu ra của các chức năng được chọn.

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

tạo ra đầu ra:

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

Trong Clojure 1.4, dotraceđã di chuyển:

Bạn cần sự phụ thuộc:

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

Và bạn cần thêm ^: động vào định nghĩa hàm

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

Sau đó, Bob là một lần nữa chú của bạn:

(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
Đẹp, nhưng làm thế nào để bạn có được clojure để tìm 'clojure.contrib.trace? Tôi có bình clojure-contrib trên đường dẫn lớp của mình nhưng REPL nóiuser=> (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
Bạn có thể viết sai chính tả clojure như là đóng cửa, hoặc đó là một lỗi đánh máy trong bình luận? Bạn có thể tải các thư viện clojure.contrib khác không?
John Lawrence Aspden

12
Kể từ 1.3, điều này đã chuyển sang clojure.tools.trace ( github.com/clojure/tools.trace )
George

4
Nếu bạn đang nhận được: "IllegalStateException Không thể tự động liên kết var không động" xem tại đây: stackoverflow.com/questions/8875353/ Lỗi
Cornelius

2
Có phải nó cũng hoạt động trong phiên bản 1.5 không? Tôi đang học Clojure với công án clojure, nhưng chưa thể bắt đầu hoạt động.
nha

100

Tôi có một macro gỡ lỗi mà tôi thấy rất hữu ích:

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

Bạn có thể chèn nó bất cứ nơi nào bạn muốn xem những gì đang diễn ra và khi nào:

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

Rất nhiều như thế clojure.tools.trace/trace.
Zaz

4
Thậm chí tốt hơn: Spyscope .
Zaz

@Zaz Tôi hoàn toàn đồng ý. Spyscope thật tuyệt vời! Thậm chí có thể tốt hơn một trình sửa lỗi. Chắc chắn để gõ.
J Atkin

66

CIDER của Emacs có trình gỡ lỗi nguồn mà bạn có thể bước biểu thức bằng biểu thức bên trong bộ đệm Emacs và thậm chí tiêm các giá trị mới. Bạn có thể đọc tất cả về nó ở đây . Ảnh chụp màn hình demo:

Gỡ lỗi CIDER


46

Phương pháp ưa thích của tôi là rắc rắc tự do printlnlên tất cả các mã ... Bật và tắt chúng dễ dàng nhờ #_macro người đọc (khiến người đọc đọc theo hình thức sau, sau đó giả vờ rằng nó chưa bao giờ nhìn thấy nó). Hoặc bạn có thể sử dụng macro mở rộng sang phần thân truyền hoặc niltùy thuộc vào giá trị của một số biến đặc biệt, giả sử *debug*:

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

Với một (def *debug* false)trong đó, điều này sẽ mở rộng đến nil. Với true, nó sẽ mở rộng để bodybọc trong một do.


Câu trả lời được chấp nhận cho câu hỏi SO này: Clojure thành ngữ cho báo cáo tiến độ? là rất hữu ích khi gỡ lỗi hoạt động trình tự.


Sau đó, có một cái gì đó hiện không tương thích với REPL của swank-clojure , nhưng quá tốt để không đề cập đến : debug-repl. Bạn có thể sử dụng nó trong REPL độc lập, dễ dàng lấy ví dụ với Leiningen ( lein repl); và nếu bạn đang khởi chạy chương trình của mình từ dòng lệnh, thì nó sẽ đưa REPL của chính nó lên ngay trong thiết bị đầu cuối của bạn. Ý tưởng là bạn có thể thả debug-replmacro ở bất cứ nơi nào bạn thích và để nó tự bật REPL khi thực thi chương trình đạt đến điểm đó, với tất cả các địa phương trong phạm vi, v.v ... Một vài liên kết có liên quan: Gỡ lỗi Clojure , gỡ lỗi Clojure -có các thủ thuật , làm thế nào để gỡ lỗi (trên nhóm Clojure Google), gỡ lỗi trên Clojars .


swank-clojure thực hiện một công việc thích hợp để làm cho trình gỡ lỗi tích hợp sẵn của SLIME trở nên hữu ích khi làm việc với mã Clojure - lưu ý cách các bit không liên quan của stacktrace bị mờ đi để dễ dàng tìm ra vấn đề thực sự trong mã được gỡ lỗi. Một điều cần lưu ý là các hàm ẩn danh không có "thẻ tên" xuất hiện trong stacktrace mà về cơ bản không có thông tin hữu ích nào được đính kèm; khi một "thẻ tên" được thêm vào, nó sẽ xuất hiện trong stacktrace và tất cả lại hoạt động tốt:

(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
Trên thực tế, có một phiên bản gỡ lỗi hoạt động với swank ngay bây giờ: hugoduncan.org/post/2010/iêu (Cảnh báo spoiler : thật tuyệt vời)

1
Phải, và thật tốt khi có một liên kết ở đây, cảm ơn! Đồng ý về tuyệt vời. :-)
Michał Marc:

Nếu đây là phong cách của bạn, bạn có thể thích thư viện debux được đề cập trong phản hồi tiếp theo. github.com/philoskim/debux
Mallory-Erik

@ Mallory-Erik Cảm ơn, tôi sẽ xem thử!
Michał Marc:

37

Bạn cũng có thể chèn mã để thả mình vào REPL với tất cả các ràng buộc cục bộ, sử dụng Alex Ostern'sdebug-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))))

Sau đó, để sử dụng nó, chèn nó bất cứ nơi nào bạn muốn thay thế bắt đầu:

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

Tôi gắn cái này vào user.clj của mình để nó có sẵn trong tất cả các phiên REPL.


16

"cách tốt nhất để gỡ lỗi mã Clojure, trong khi sử dụng thay thế"

Hơi bên trái trường, nhưng 'sử dụng bản thân REPL'.

Tôi đã viết Clojure sở thích trong hơn một năm và không cảm thấy cần một công cụ sửa lỗi nào. Nếu bạn giữ các chức năng của mình nhỏ và chạy từng chức năng với các đầu vào dự kiến ​​tại REPL và quan sát kết quả thì có thể có một bức tranh khá rõ ràng về cách hoạt động của mã của bạn.

Tôi thấy một trình gỡ lỗi là hữu ích nhất để quan sát STATE trong một ứng dụng đang chạy. Clojure giúp bạn dễ dàng (và vui vẻ!) Để viết theo kiểu chức năng với các cấu trúc dữ liệu bất biến (không thay đổi trạng thái). Điều này ồ ạt làm giảm nhu cầu sửa lỗi. Một khi tôi biết rằng tất cả các thành phần hoạt động như tôi mong đợi (đặc biệt chú ý đến các loại sự vật) thì hành vi quy mô lớn hiếm khi là một vấn đề.


Điều này chủ yếu là đúng nhưng khi bạn có đệ quy trên nhiều hàm chẳng hạn thì không dễ dàng gì.
Giăng

9

Nếu bạn sử dụng emacs / slime / swank, thì hãy thử điều này tại REPL:

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

(factorial 30)

Nó không cung cấp cho bạn một dấu vết ngăn xếp đầy đủ như bạn nhận được trong LISP, nhưng nó tốt cho việc chọc ngoáy.

Đây là công việc tốt của:

http://hugoduncan.org/post/2010/swank_clojure_gets_a_break_with_the_local_en môi.xhtml

như đã được đề cập trong một bình luận ở trên.


9

Đối với IntelliJ, có một plugin Clojure tuyệt vời có tên là Cursive . Trong số những thứ khác, nó cung cấp REPL mà bạn có thể chạy trong chế độ gỡ lỗi và chuyển qua mã Clojure giống như bạn làm, ví dụ như Java.

Tôi sẽ trả lời câu trả lời của Peter Westmacott mặc dù theo kinh nghiệm của tôi, việc chạy các đoạn mã của tôi trong REPL hầu hết là một dạng gỡ lỗi đầy đủ.


Tôi đã sử dụng La Clojure thành công, nhưng dường như sắp chết vì sự khó chịu bây giờ github.com/JetBrains/la-clojure/blob/master/README.md
leeor 15/08/2015

Nhưng làm thế nào để gỡ lỗi Leiningen, nó cho thấy:Error running 'ring server': Trampoline must be enabled for debugging
Gank

Điều đó dường như là cụ thể ringhoặc lein- có lẽ đáng để đăng một câu hỏi riêng biệt?
dskrvk

6

Kể từ năm 2016, bạn có thể sử dụng Debux , một thư viện gỡ lỗi đơn giản cho Clojure / Script hoạt động cùng với bản thay thế cũng như bảng điều khiển của trình duyệt của bạn. Bạn có thể rắc macro dbg(gỡ lỗi) hoặc clog(console.log) trong mã của mình và dễ dàng quan sát kết quả của các chức năng riêng lẻ, v.v., được in ra REPL và / hoặc bảng điều khiển của bạn.

Từ Readme của dự án :

Sử dụng cơ bản

Đây là một ví dụ đơn giản. Macro dbg in một biểu mẫu gốc và in đẹp giá trị được đánh giá trên cửa sổ REPL. Sau đó, nó trả về giá trị mà không can thiệp vào việc thực thi mã.

Nếu bạn bọc mã bằng dbg như thế này,

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

sau đây sẽ được in trong cửa sổ REPL.

Đầu ra REPL:

dbg: (+ 10 20) => 30

Dbg lồng

Macro dbg có thể được lồng nhau.

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

Đầu ra REPL:

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

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


5

Hugo Duncan và các cộng tác viên tiếp tục làm công việc tuyệt vời với dự án ritz . Ritz-nrepl là một máy chủ nREPL với khả năng gỡ lỗi. Xem Debuggers của Hugo trong Clojure nói chuyện tại Clojure / Conj 2012 để thấy nó hoạt động, trong video một số slide không thể đọc được vì vậy bạn có thể muốn xem các slide từ đây .




1

Đây là một macro đẹp để gỡ lỗi các lethình thức phức tạp :

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

... Và một bài luận giải thích việc sử dụng nó .


-4

Phiên bản chức năng của def-let, biến một let thành một loạt defs. Một số tín dụng đi đến đây

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

Cách sử dụng: Cần trích dẫn nội dung bằng một trích dẫn, vd

(def-let '[a 1 b 2 c (atom 0)])
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.