
Notes on debugging Clojure code - melqdusy
http://eli.thegreenplace.net/2017/notes-on-debugging-clojure-code/
======
lvh
A few more tricks:

\- CIDER, the de-facto Clojure environment for Emacs, has a debugger[1]. This
is also true for some of the larger IDEs, like Cursive.

\- Timbre[2] has a number of cool debugging macros, especially spy, which you
can tack onto any expression and it'll log it for you. Very useful, similar to
the macro in the post, except you don't have to write it :)

I really can't overestimate how valuable a real REPL-driven development
environment is.

[1]: [https://github.com/clojure-
emacs/cider/blob/master/doc/debug...](https://github.com/clojure-
emacs/cider/blob/master/doc/debugging.md) [2]:
[https://github.com/ptaoussanis/timbre](https://github.com/ptaoussanis/timbre)

~~~
hacker_9
I find debugging + error messages to be the worst part of Clojure.

In CIDER I have to mark my functions as debuggable before I can step into them
- this is an odd requirement and a PITA when the functions are spread out
through the codebase. It's so easy to accidentally commit a non-debuggable
version as well.

Cursive is a lot better and more modern, with one painful problem: no proper
support for 'break on exception'. So on exception the program just closes and
I'm just left with a stacktrace in the console. You can turn on 'Java
Exceptions', but this has multiple problems:

1\. On load of Clojure files there is a ton of Java exceptions thrown,
literally making it unusable

2\. and if you disable those exceptions in the Condition field the boot up
process takes _forever_ to actually run (likely because it's interpreting the
Condition field code over and over and over again..).

Any good solutions to these?

~~~
emidln
>In CIDER I have to mark my functions as debuggable before I can step into
them - this is an odd requirement and a PITA when the functions are spread out
through the codebase. It's so easy to accidentally commit a non-debuggable
version as well.

When I'm connected to my repl, I often want to debug things I didn't mark as
debuggable (hint: nothing is ever debuggable by default). I can jump to
definition, toggle debugging, and then run some code (usually from a comment
block or my scratch/user.clj (gets excluded by gitignore)). When I'm done, I
can toggle it off or simply re-eval the function. In spacemacs, the normal
mode binding for this is

    
    
        , d b

------
yjgyhj
After being Clojure-only for a long time, I decided to write the front end to
my last project in Elm, just to see what the fuzz was about.

While I miss code-is-data very much, there is no turning back from the type
system and how there are no unpure functions. It is just so mind-blowingly
easy to catch almost every bug I would usually write.

I'm afraid this is the point where I should try Haskell, and be unsatisfied
for the rest of my professional life.

~~~
ealhad
You should definitely try, even if you end up being somewhat unsatisfied every
time you have to use something else.

When I write code in Haskell, it feels right, just like when I discovered
Lisp. Only better. And oh, the purity! Not only in the mathematical sense,
even the code is completely deprived of clutter.

~~~
penpapersw
But at what cost!? Every time I write Haskell code it reads like a
mathematical proof that's beyond my ability to comprehend! Whereas my Ruby,
Python, and Clojure programs read like a poem I might have written in third
grade.

~~~
ealhad
I don't think Haskell code looks like this. You don't have to use all the guru
syntactic sugar; the library functions usually have pretty explicit names, and
yours should have to.

------
oskarth
Another trick is to define temporary inline vars. If I have some Ring handler,
it can be quite annoying to construct a request by hand. Instead, I can do
something like:

    
    
        (defn foo-handler [{{:strs [authorization]} :headers :as req}]
          (def *req req)
          (def *auth authorization)
          ...)
    

Then fire off a request in my browser and simply proceed to play around with
`req` and `auth` in my editor/REPL, which is where I am running my local
server from.

~~~
dragandj
This is a terrible advice, and is not even necessary. Even println debugging
is better than this. Not only defs are abused contrary to their intended use,
they are difficult to distinguish from legitimate defs, and thus some may (and
will!) slip away into the wild.

~~~
oskarth
> Even println debugging is better than this.

Print debugging is great. A carefully placed print statement is something many
great programmers swear by [1], despite the existence of "sophisticated"
debuggers (see footnote in OP). This is a complement to this when you want to
play around with the data directly.

> Not only def's are abused contrary to their intended use, they are difficult
> to distinguish from legitimate defs, and thus some may (and will!) slip away
> into the wild.

Never been an issue for me. It's just an extension and morphing of playing
around with data in a standalone REPL. The temporary defs are never used in
actual code logic, the prefix character makes it clear it is purely for
exploration. Is it a dirty hack? Yes, but one might argue that's partly what
Lisp is too: _The most intelligent way to misuse a computer_ \- Dijkstra
(paraphrasing) Lisp

Some unsolicited feedback: you might want to consider that there is more than
one way to skin a cat. If you want to understand the trade-offs of solutions
people find, you might find it useful to not dismiss solutions outright.
Especially something as universally useful like print debugging, considering
how many people use it. Instead, a more useful question to ask yourself is:
why do experienced programmers use print debugging when they are aware of the
existence of debuggers? That might expand your worldview, as opposed to
categorizing these people as "doing it wrong". See _Worse is Better_ etc.

1: From this lovely quote by Brian Kernighan: _The most effective debugging
tool is still careful thought, coupled with judiciously placed print
statements._

~~~
dreish
Why not define debug-req and debug-auth as atoms before this function, and set
them with (reset! debug-req req) etc.?

------
Karunamon
Slight tangent: Am I the only one that's a little bit annoyed that it seems
like you need to start using the complex beast that is Emacs to get first
class Lisp support? There are one-off apps, but everyone swears by SLIME mode
or similar.

Yes, I get it. The editor itself is written in Lisp. It's the most hackable
thing out there. Yes, fine. It is a masterpiece of technical achievement.
That's all well and good, but...

I just want to learn the language, not the language _and_ an editor that seems
entirely alien at the same time. If I _had_ to learn vim as a prerequisite to
learning and writing Python (specifically using the debugger and a REPL), I
probably would have never learned Python.

~~~
lispm
It makes sense to use Lisp-specific tools with Lisp, otherwise the whole
experience is underwhelming. The best Lisp tools are usually written in itself
or some variant of it.

On the Mac you can download Clozure Common Lisp from Apple's Macintosh
application store:

[https://itunes.apple.com/us/app/clozure-
cl/id489900618?mt=12](https://itunes.apple.com/us/app/clozure-
cl/id489900618?mt=12)

It comes with a small IDE written in itself.

A lot of people who use CL as a serious hobby or for work use one of the
commercial development environments: Allegro CL or LispWorks.

There are several other (some are obscure) options, but many use GNU Emacs /
Slime or its fork Sly.

~~~
SomeHacker44
I would love a proper "hobbyist" priced license for a full 64-bit LispWorks at
around $200.

Stephen Wolfram did it for Mathematica (Home license)...

~~~
lispm
The LispWorks 64bit hobbyist license costs 600 Euro. It is not tied to a
single machine and can run more then one process.

------
mitsoz

      When it comes to debugging, I'm firmly in the printf camp; I rarely prefer debuggers over printf-based debugging
    

I'm struggling to understand this. Why would you ever prefer printf debugging
in your local dev environment over using a debugger?

~~~
jhbadger
Language and environment independent. It just works. It's like IDEs -- so many
people spend so much time writing and learning IDEs specific to a language
rather than just picking a standard text editor that can be used for any
language on any platform.

~~~
zdkl
Yeah this. Does this thing colour parenthesis and stdlib builtins? We're good
to go.

------
nXqd
I have years working experience with Clojure. The tooling in Clojure is much
better than this. If you want a debugger, Cursive has it out of the box, if
you are used to Intellij, you can use it right away. If you are an Emacs user,
then you know your way around CIDER and its tooling. It's not that I don't
agree with the author's post. But I mean for simple functions, good enough
tooling and unit tests are your friends.

------
lispm
I fear Lisp had already a better debugging experience in the mid to late
60s...

~~~
dragandj
Clojure also has better debugging experience. This is a bare-bones workflow of
a guy who doesn't use the best tools available in Clojure.

~~~
blain_the_train
Could you provide links to this better debugger

~~~
blain_the_train
Found them in other comments, I don't see anything that's native to clojure
though, not like pdb is for python at least

~~~
emidln
Debugging, like most things in clojure, is a library. You can view the
implementation (as an nREPL middleware) here[1]. It's possible to use this
debug middleware from any random nrepl client, but I'm not aware of any that
actually implement a command-line debugger such as you get with python's pdb.
I suspect, but don't have any time or inclination to verify, that implementing
a basic command-line debugger using this library would be a weekend hack. I
suspect this debugger is far more useful integrated into your editor than
stand-alone since clojure isn't really a file/line-oriented language (which
would make selecting forms hard).

[1] [https://github.com/clojure-emacs/cider-
nrepl/blob/master/src...](https://github.com/clojure-emacs/cider-
nrepl/blob/master/src/cider/nrepl/middleware/debug.clj)

------
tsm
Also worth noting that Cider (the most-commonly-used Clojure plugin for Emacs)
has had a decent debugger for a couple years: [https://github.com/clojure-
emacs/cider/blob/master/doc/debug...](https://github.com/clojure-
emacs/cider/blob/master/doc/debugging.md)

Cursive (Clojure for IntelliJ) does too.

------
Dimi9909
interesting to see Eli as a famous Python lover started to try Clojure.

