While working on pynocle some time ago, I found myself getting away from TDD and going back to the more traditional “run-debug-fix” pattern. Write code you think is correct, run it to see if it is, if it isn’t, stick a breakpoint and see what’s wrong, change code, repeat until there are no problems.
While this can often be the quickest way to get something working, it ultimately and always comes back to bite. I’m happy that I’ve gotten to a point with TDD where I notice this behavior and it makes me feel dirty. Though not always dirty enough to stop it, especially if I’m in a difficult-to-test environment depending on modules I can’t run from pure python.
The problems with run-debug-fix are many.
- The code you are writing is difficult enough that you didn’t write it correctly the first time. So what makes you think you or someone else is going to have an easy time debugging or understanding it in the future.
- If the bug was logical, there was obviously some context, state, or situation you had not thought of. How are you sure you will remember this context or situation when you change the code in the future? How can you communicate that your code is relying on a certain state somewhere else?
- If your design is not testable, you are making it even less testable by adding more implicit logic where you’re fixing the bug. Implicit logic that is going to be very difficult to test for when you come back later and forget about it.
- Most importantly: Every bug you fix or feature you add using run-debug-test is a doubly negative activity. -1 for the reasons above and -1 for the missed opportunity to add a test. It would be better to leave the bug there or delete the offending code entirely. You are increasing the complexity of your software by supporting another code path that did not previously work or exist, instead of increasing the stability of your software by adding tests.