Exploring code in space and time
I have a confession to make. It's actually pretty embarassing. Until recently, I'd never really learned to use a debugger. I'd played with them, sure, but never used them as a first line of defense. And as with most hard things, I wish I'd learned to wield them sooner.
I blame this on the fact that my most formative programming years were spent in pure functional languages. When I'm dealing with a functional language, I think of my program in terms of call trees. Pure, stateless call trees that operate atomically on shared concurrent data types. Because state doesn't change in these functions, there's no notion of time. And as a result, it's meaningless to use a debugger on them. But you really don't need to; the beauty of a pure function is that you can fully understand how it runs every time just by reading it. It doesn't depend on the outside world.
And because the data shared between these pure call trees is explicitly defined, it's easy to log every change. Logging gives you the same picture as debugging.
Now I write in blend of imperative and functional and logging has blind spots. I just can't know every state change that I might want to log in a large program. Debugging allows me to inspect the nooks and crannies in my running program I don't want to instrument with logging ad hoc.
If text editors allow me to explore a program in space, logging and debuggers allow me to explore them in space-time. I get to pull out a slice of the running program and look at it, poke it, understand it. And they're wonderfully complementary. Logging allows me to choose a specific set of slices to be the canonical slices and generate them for study. Debugging allows me to think up slices on the fly that I don't necessarily want to become canonical and study those on the fly.
My recent re-introduction to debuggers has me appreciating a running program as a structure to understand on its own. I'd gotten used to mostly exploring code in space; here's to exploring them in space and time.