Debugging fast and slow
When my code was not working yesterday, I started to apply quick fixes, almost re-actively. I was in fast thinking mode. I also was tired and found it hard to reason properly about what‘s going on. Interestingly, coming back to it a bit later it was clear what was going on and I did apply the right fix easily.
Being refreshed I could approach the issue in slow thinking mode. I observed. I wrote down my hypotheses as asserts. I worked in small steps, making sure the error was not with parts of the code I was not actively working on.
Some tools have been proven helpful over and over again. One of them is writing down type annotations. And I‘m not talking about type annotations the compiler understands, rather type annotations for myself. I tend to use a style inspired by Haskell‘s types, for example Monad m => (a -> b) -> m a -> m b
.
The other tool that is helpful is writing down examples. (Indeed, that‘s one of the steps of the Design Recipe in How to design programs for good reason.) Often it turns out that I do not have a clear idea yet, how a function should deal with specific examples. By writing things down, things get clearer. This also necessitates to break the program down in sufficiently small parts that can actually be reasoned about.
But sometimes the code seemingly works, I introduce the next layer and it stops working as expected. I make the necessary change and something else stops working. There‘s a conflict between two ways of behavior and I haven‘t found a clear picture of how to resolve it yet.
This is an opportunity to write down the conflicting behaviors and derive from this the spark for the proper design direction.