A language agnostic debugging list
Checklists are nice cognitive tools. This one can also work as a reminder when designing code. Please come with feedback and suggestions for improvement.
It is not always the case that we immediately spot the reason for an error in a codebase. Sometimes it is hard to find due to the design. In other cases the definition of correct behaviour is the problem. It might also be a lack in understanding when it comes to the technology used. This checklist is useful regardless of your knowledge of the language you are debugging. Depending on domain knowledge, technical knowledge and the state of the code base, how long the steps take and what information they give, will vary. This is, as most knowledge work, best done together with someone else, or with an entire team.
A test
Write a test, of some kind, that captures the unintended behaviour. In worst case it is a description of steps to take written on a piece of paper. Best case is a unit test. This will define where we see the problem, with its setup and the state it leaves the program in.
Specifications
Do we know for sure that this is a problem, not just a state where the behaviour is not clearly defined? Make sure that we know what the code is supposed to do in this particular case.
Location
Are we sure that this is where the problem is caused? Check that the in-data is correct, and that the data out is not as expected. Try to narrow it down, define as little code as possible where the error might be.
Regression
Has it ever worked? If it has, what has changed since then? Version control is your friend here.
Static analysis
Is the IDE saying anything? A real development environment uses parts of the same algorithms as the compiler/transpiler/interpreter, to be able to give hints and syntax highlighting. There is also often static code analysis tools that can highlight practises that make the code fragile. Use tools.
Surrounding state
Are there inputs, not stated as such, in the code we are looking at? Some code use state that is external in some way. One way to check this is by trying to write a unit test, instead of an integration test. What do we need in our setup to make it work? The state could be a global variable used by a collaborator, today’s date, or some settings in the system. Make sure that we know all the actual input data to the code we are investigating. Try to make the implicit input explicit when testing the code.
Comparing
If there is comparing going on, is it done correctly (for that language)?
Things missing
Do we know what could be null/nil/empty? Keeping null-checks at the boundaries of code makes this easier. Use the tools the language provide to deal with missing data.
Data structures
Are data-structures used as intended? If iteration happens, are the boundaries the right one? Do the data-structures have any quirks in this framework?
Language specifics
Are there other language features that could be a problem? In Python, default arguments can turn into singletons. In ObjectiveC a method call on a deallocated object will return 0, or nil, but succeed. Try to rewrite the code, even if it turns it bulkier, just to see if misdirected “cleverness” might be the problem. Then read the documentation to understand what happens if the behaviour changes.
Flow
Is the flow of execution as we think it is? Use a debugger to step through the calls, following the control flow. Test cases can also be run in debug-mode. Make sure that there are no code skipped. One example is if code is executed as part of a condition, that is already evaluated as true, and therefore skipped.
Threads
Are we working with asynchronous code? If we are, then make sure that it is done correctly. Test ways to make sure that all of the suspect code is run synchronous, to eliminate logic errors that are not timing related , even if the code can never run like that in production.
Identity
Is everything what we think it is? Is the runtime scope clear? Do we have inheritance, this/self references, variables or functions that might shadow each other, or other things that might not be what we assume that they are? Do we reuse variable names or references? Might there be things where ownership is unclear and it is garbage collected? Use a debugger, or print statements, to check that things are what we expect. (Playing Destiny’s Child while doing this is optional, but it is a good reminder that scope in JavaScript is tricky.)
Self care
Take a break, if you have not already. At this point I might start searching the internet, or try writing a question at Stack Overflow. (Often the solution is in writing the post, not posting it.) I might also call a friend. My experience is that the harder the answer is to find, on the internet, the more likely it is that the mistake made is really basic.
Hope this list helps. I will use it the next time I get stuck on code not working as I intended.
How do you find your mistakes? Do you have anything to add? Have I missed anything language specific that is not included in the steps above?
Comment on the blog!
Create a comment by emailing me.
Open an email to start writing.I will then add the comment to the post.