Debugging, like washing the dishes or doing the laundry, is one of those things that programmers hate doing but just have to do anyway1. Fortunately, just as in household chores, applying the right tools --- gdb, washing machine, dishwasher --- go a long way in ameliorating our pain. In our daily lives there (sadly) are a myriad things to clean, each with its own washing tool, yet when it comes to debugging the myriad of programming languages and paradigms, we seem to think the same strategy of state inspection at user-specified execution breakpoints applies equally to everybody. Not good.
For instance, suppose a complex nested SQL query fails to produce a tuple it should have and we want to know why. In this case it is not very helpful to step through the source lines of the query, or even its sub-queries. We already know the sequence of execution: each operation (join, select, filter, sort, etc) is going to be executed exactly once, presumably in bottom-up order. What we want to know is where the tuple got lost. Can we inspect the intermediate states? It depends, if the queries are large and complex enough that would not be too easy either.
Another example is Haskell, the quintessential functional language. Functional languages often eschew the loops and branches of imperative languages in favor of recursion and pattern matching. Since unlike loops and branches, related patterns do not need to be defined in the same syntactic locale, tracing through the execution of a Haskell program will send the programmer jumping around in the source file, with little hope of knowing what to expect next. This is also true for Prolog debuggers that signal when a rule fires. Additionally, Haskell possesses an incredibly powerful and complex type system whose type declarations almost constitute its own Turing-complete sub-language. One of the biggest frustrations novices face are the cryptic error messages Haskell emits whenever they get one of these type declarations wrong, a problem debuggers based on breakpoints can do little to help. Some of these issues may explain why despite the availability of more traditional debuggers like Hood, Haskell programmers still find a testing tool like QuickCheck to be their weapon of choice.
Perhaps even more poignant examples come from DSLs. How would we run a debugger on an Ant script or a C++ make file? What about a Antlr grammar that contains an ambiguity? Or a Django template that renders the wrong page? Skeptics will now say we should stop coddling programmers with debuggers and tell them to read their code carefully. Right. With a large enough code base and without the right tool support, we find ourselves back to scrubbing the bathroom floor with a toothbrush.
So are we using the dishwasher to do the laundry?
1. That said, some of the most brilliant hackers who seemingly never need debug their code also seemingly never do any of these other things. Which is why we give them their own smell-proof cubicles.
For instance, suppose a complex nested SQL query fails to produce a tuple it should have and we want to know why. In this case it is not very helpful to step through the source lines of the query, or even its sub-queries. We already know the sequence of execution: each operation (join, select, filter, sort, etc) is going to be executed exactly once, presumably in bottom-up order. What we want to know is where the tuple got lost. Can we inspect the intermediate states? It depends, if the queries are large and complex enough that would not be too easy either.
Another example is Haskell, the quintessential functional language. Functional languages often eschew the loops and branches of imperative languages in favor of recursion and pattern matching. Since unlike loops and branches, related patterns do not need to be defined in the same syntactic locale, tracing through the execution of a Haskell program will send the programmer jumping around in the source file, with little hope of knowing what to expect next. This is also true for Prolog debuggers that signal when a rule fires. Additionally, Haskell possesses an incredibly powerful and complex type system whose type declarations almost constitute its own Turing-complete sub-language. One of the biggest frustrations novices face are the cryptic error messages Haskell emits whenever they get one of these type declarations wrong, a problem debuggers based on breakpoints can do little to help. Some of these issues may explain why despite the availability of more traditional debuggers like Hood, Haskell programmers still find a testing tool like QuickCheck to be their weapon of choice.
Perhaps even more poignant examples come from DSLs. How would we run a debugger on an Ant script or a C++ make file? What about a Antlr grammar that contains an ambiguity? Or a Django template that renders the wrong page? Skeptics will now say we should stop coddling programmers with debuggers and tell them to read their code carefully. Right. With a large enough code base and without the right tool support, we find ourselves back to scrubbing the bathroom floor with a toothbrush.
So are we using the dishwasher to do the laundry?
1. That said, some of the most brilliant hackers who seemingly never need debug their code also seemingly never do any of these other things. Which is why we give them their own smell-proof cubicles.