Want to become better at debugging?


#1

Hello friends,

I made a super small library, which I believe works in all flavours of Clojure (I’ve tried it in JVM/CLR). It has zero dependencies. Check it out! It’s just 79 lines.


It’s wholly inspired by proto-repls save.

The gist of it can be found at the bottom:

(comment
  ;; Example usage
  (defn add [x y] (save :a) (+ x y))
  (add 5 10)
  (ld :a)
  x ;; 5
  y ;; 10
  (add 20 30)
  (add 7 13)
  (print-saves :a))

The point of this library is to showcase a way of debugging, which can be seen as the rest to breaks first. What I mean by this is that if we call a function, let’s say the aforementioned add, three times:

(add 1 1)
(add 2 2)
(add 3 3)

If we were to have a breakpoint in add, we would generally get the most information the first time we call the function. Of course we could exit the first breakpoint and go to the next incantation of add, but if we imagine add being called 1000 times, that process would get extremely painful in the long run.

So what do we do when first isn’t what we want? We call rest! In this case, save. As I showed in the example code at the beginning, save stores all local variables at the point it is called. These variables can then later be loaded by using ld! What this leads to is that you don’t have to know beforehand where to call break, but instead storing all your data and later being able to filter through it.

Currently you have to do some manual work in order to filter through the data, since ld just fetches the last stored data at a specific key. But at least you have the possibility! I hope you’ll have some use for this. :slight_smile:

Bonus! If you only want to store data when an error occurs, it’s super easy to do things like:

(defn add [x y]
  (try (+ x y) (catch Exception e (save :err) (throw e)))

You get the added bonus of the error information being stored together with the variables leading to the erronous call! :smiley: Combine it with your editor’s equivalent of eval-last-sexp and you’ll be able to run different parts of your function using the variables that ld defd for you. Makes for super easy debugging!


#2

This is wonderful.

Not sure what ”all flavours of Clojure” means. I just tried it with ClojureScript and can’t get your example to perform its miracles.

(print-saves) => nil

The compiler does not complain when requiring the miracle namespace and there is no error for the (save :a).

What gives?


#3

Ah, how typical. The flavours I’ve tried are JVM/CLR. I’ll try CLJS in a minute, hopefully I can figure it out. :slight_smile:


#4

Nice! Reminds me of scope-capture.


#5

Sure, it’s not an unique idea. But sadly I don’t see many other developers utilizing it. :slight_smile:


#6

Scope capture debugging is a nice approach. Having as few debugging dependencies as possible is also important. Nice work!

I’ve been using tap> very heavily lately for debugging. I always have a REBL instance running and when debugging I can just add calls to tap> into my functions (hot-key to redef a version with added debugging) and then I can watch the Tap panel in REBL as my code runs. Having a scope-capturing version of tap> would be really nice, along with some filtering in the Tap UI.


#7

Thanks for sharing sean. :slight_smile: What you described with filtering etc sounds like something I’ve messed around with a bit using unitys UI. So far we don’t have datafy in Arcadia (still waiting for cljr 1.10), but when we do I have plans on adding UI for filtering saved locals e.t.c. I’ve also worked a bit on macros for saving as much data as possible when exceptions are thrown, in order to easily replicate the error. :slight_smile:

Hotkey for redefing sounds neat, is the hotkey in REBL then? Or do you do it from the editor?


#8

Just from my editor. I use Atom and Chlorine which has built-in support for "inspect"ing values in REBL (as well as typical eval file / top-level form / current form).


#9

Aha, sounds cool. :slight_smile: Do you have a screencast or similar of this? I can imagine it, but would be cool to see as well!


#10

I’d love me a screencast too!


#11

Another request for a screencast (or some other format you find more suitable) , @seancorfield! I’d really like to know what you do that’s better to do in REBL than in a CIDER REPL.


#12

I’m old-fashioned and have no idea how to do screencasts (and I’m mostly working on a proprietary codebase so I couldn’t share my workflow on that anyway).

As for CIDER, I’ve no idea since I stopped using Emacs years ago.

For the most part, I’m really just doing the same thing Stu Halloway advocates in things Debugging with the Scientific Method and his various talks and writings on debugging via the built-in tools you have in the REPL itself.


#13

So Chlorine is an unrepl client for Atom? With added support for ClojureScript repls as well?


#14

I can’t speak to cljs support as I haven’t touched cljs since early 2015. Chlorine is written in cljs tho’ and with shadow-cljs locally, there’s a very nice workflow to hack on the source of it while you’re editing, and it auto-reloads. Also, the maintainer Mauricio Szabo is a joy to work with, in terms of support and discussions around enhancements etc.

It’s a Socket REPL client, that happens to use unrepl (at least for now). I like that it has built-in support for REBL. I love that it only requires a Socket REPL! That means I can start any sort of Clojure-based process, with JVM options to start a Socket REPL, and connection Atom to it – local processes, REBL, even production processes (and, yes, I have debugged a few production processes directly from Atom using Chlorine! :slight_smile: ).