luminus模版生成的项目中断点无效
luminus模版生成的项目中断点无效
描述
有些同学提到Clojure项目中, 设置断点后无效, 所以尝试复现这个问题.
重现
创建一个全新的后台服务开始
lein new luminus debug-test +service默认生成的模版项目中, 在/plus这个路由下挂载的函数, 增加debug
#dbg (defn plus [x y] (debug "plus working....") (+ x y))手动eval这个函数后通过swagger测试plus接口, 确实没有断住.
问题分析
不要慌
事必有因
从原理出发
函数是一个java对象, 此对象实现了IFn接口, 以inc函数为例
(ancestors (class inc))
=> #{java.lang.Runnable
clojure.lang.IObj
java.io.Serializable
java.util.concurrent.Callable
clojure.lang.IFn
clojure.lang.AFn
java.util.Comparator
java.lang.Object
clojure.lang.AFunction
clojure.lang.Fn
clojure.lang.IMeta}
在repl里我们能看到这个对象, 对象的toString能看到该对象的hash值:
(defn plus [x y]
(debug "plus working....")
(+ x y))
(.toString plus)
=> "debug_test.routes.services$plus@7af7a9d"
可以debug的func是一个经过特殊eval的func, 替代到默认的func版本
#dbg
(defn plus [x y]
(debug "plus working....")
(+ x y))
(.toString plus)
=> "debug_test.routes.services$eval24320$plus__24321@7231d787"
发现
在接口请求以后, 我们的plus函数对应的对象又变了!!
(.toString plus)
=> "debug_test.routes.services$plus@2fba93e5"
有这么一个中间件
在文件 dev_middleware.clj 中, 有这么一段:
(defn wrap-dev [handler]
(-> handler
wrap-reload ;; wrap-reload 的作用是在src目录下的文件更新后, 自动relaod这个ns
wrap-error-page
(wrap-exceptions {:app-namespaces ['debug-test]})))
每一个http请求, 会触发wrap-reload 的源代码目录中修改文件的重新编译. 带来的好处是, 只要代码改了, 你一定能看到修改的效果. 坏处显而易见, debug 没有起到作用
(defn wrap-reload
"Reload namespaces of modified files before the request is passed to the
supplied handler.
Accepts the following options:
:dirs - A list of directories that contain the source files.
Defaults to [\"src\"].
:reload-compile-errors? - If true, keep attempting to reload namespaces
that have compile errors. Defaults to true."
([handler]
(wrap-reload handler {}))
([handler options]
(let [reload! (reloader (:dirs options ["src"])
(:reload-compile-errors? options true))]
(fn
([request]
(reload!)
(handler request))
([request respond raise]
(reload!)
(handler request respond raise))))))
最终的答案

所以, 解决这个问题, 只要不触发中间件的编译就好了
- 不用中间件, 把中间件这一行删掉
- 以不触发中间件的方式(不修改文件)生成调试调试编译(debug compiling)
更深入的了解
下面的每一个小节都值得一片长文, 放在这里仅仅是个路标.
Clojure的var
(def a 1)
(type a)
;; => java.lang.Long
(type #'a)
;; => clojure.lang.Var
(symbol? 'a)
;; => false
Clojure的reader
同其他lisp一样, reader是clojure显著的一个特性, 甚至说repl就是reader的副产品
reader tag
#dbg 和 #break是cider中引入的新reader tag 就像clojure语言已经内置的几个reader tag一样:比如
- #inst : 时间的字符串表示
- #uuid : uuid字符串表示
- #? : 条件编译
@(def a #?(:clj "clj") #?(:cljs "clojurescript")) ;; => "clj" [1 2 #?@(:clj [3 4] :cljs [5 6])] ;; => [1 2 3 4] {:date #inst "2022-05-14T11:53:07.699-00:00" :uuid #uuid "2700cb34-04bc-448a-8a48-bd4f42fd2cbf" }用reader实现一个debugger
这是来自 The Joy of Clojure 的一个例子, 读懂这个例子要理解clojure的macro.
(defn contextual-eval [ctx expr] (eval `(let [~@(mapcat (fn [[k v]] [k `'~v]) ctx)] ~expr))) (defmacro local-context [] (let [symbols (keys &env)] (zipmap (map (fn [sym] `(quote ~sym)) symbols) symbols))) (defn readr [prompt exit-code] (let [input (clojure.main/repl-read prompt exit-code)] (if (= input ::tl) exit-code input))) (defmacro break [] `(clojure.main/repl :prompt #(print "debug=> ") :read readr :eval (partial contextual-eval (local-context))))接下来我们使用这个自定义的调试器
(defn div [n d] (break) (int (/ n d))) (div 10 0) debug=> n ;;=> 10 debug=> d ;;=> 0 debug=> (local-context) ;;=> {n 10, d 0}
Clojure web开发
luminus 模版
非常方便的创建各色clojure/clojureScript项目 链接
ring的中间件
每一个http请求, 都会触发一些列的中间件操作

编辑器cider/calva都做了什么
以emacs为例, 快捷键 M-x cider-debug-defun-at-point 的工作
读一下cider的源代码, 就是在debug的函数外面套了个 #dbg, 没啥花里胡哨的.
(defun cider-eval-defun-at-point (&optional debug-it)
"Evaluate the current toplevel form, and print result in the minibuffer.
With DEBUG-IT prefix argument, also debug the entire form as with the
command `cider-debug-defun-at-point'."
(interactive "P")
(let ((inline-debug (eq 16 (car-safe debug-it))))
(when debug-it
(when (derived-mode-p 'clojurescript-mode)
(when (y-or-n-p (concat "The debugger doesn't support ClojureScript yet, and we need help with that."
" \nWould you like to read the Feature Request?"))
(browse-url "https://github.com/clojure-emacs/cider/issues/1416"))
(user-error "The debugger does not support ClojureScript"))
(when inline-debug
(cider--prompt-and-insert-inline-dbg)))
(cider-interactive-eval (when (and debug-it (not inline-debug))
(concat "#dbg\n" (cider-defun-at-point)))
nil
(cider-defun-at-point 'bounds)
(cider--nrepl-pr-request-map))))