Debugging: Listing Your Assumptions

Joël Quenneville
The good thing about computers is that they do what you tell them to do. The bad news is that they do what you tell them to do.
Civilization VI, flavor text when discovering the computers technology

Code is a form of communication. It is communicating with your future self and colleagues that will need to interact with it. At its most fundamental level a program is a series of instructions to a computer. Computers will do exactly what we tell them to, so when they misbehave it’s usually because we have miscommunicated our instructions (we call this a “bug”).

As with human-to-human interactions, miscommunication with computers is often due to false assumptions and different understandings of context. In our minds, those assumptions are so obvious that they don’t even need to be mentioned – they are implicit. Other times, we may not even be aware that we have made an assumption.

This post is part of our Debugging Series 2021

Implicit into explicit

To get a better idea of where the disconnect happened between us and the computer, we need the self-awareness to know that we have made assumptions and the clarity to explain what those assumptions are. That is to say, we need to turn those implicit assumptions into explicit assumptions.

Once we do this, the issue often becomes immediately obvious. We may have made an assumption that was just plain wrong. We may also realize that some context isn’t quite what we thought.

Sometimes this can be enough to solve the bug outright. At other times, this will be just the start of our investigation. Once we have have a bunch of plausible assumptions, we can’t just trust them! They will need to be validated.

Here are some concrete ways to make our assumptions more explicit.

Writing a list

One of the simplest ways to “list your assumptions” is to, well, write a list. Grab a pencil and paper or fire up your text editor and start writing! What are some things you believe are true about your system?

Beware! Programmers believe a lot of falsehoods about the world around us.

Ruled paper with handwritten list of assumptions. These are: time only moves forwards, we always send email after purchase, users must be signed-in to purchase, the size of our inventory is always larger than the number of items in the users cart, and timezones occur in 1 hour intervals.

Pairing

Implicit assumptions are often poorly thought through and fuzzy because they’re just sitting around in your head.

Pairing can help get some clarity. Sometimes your pair doesn’t need to say anything. You begin by explaining the situation. As you do that, you naturally express some of your implicit assumptions. When you hear them out loud, you immediately realize which ones don’t hold true.

Sometimes you aren’t even aware that you have an assumption. These are the infamous unknown unknowns. Pairing is a fantastic way to flush these out because you and your pair will have differing sets of unknown assumptions. As you work together, your different mental models will clash, forcing you to become self-aware of your assumptions as well as to make them explicit so you can explain them to your pair.

Two developers pair-programming

Decision tables

Two common forms of incorrect assumptions we have about software are:

  1. We assume the specification for the software is correct
  2. We assume the software correctly implements the specification

Decision tables are a tool for systematically listing out all the different things a piece of software does in different situations and with different inputs. You can use it to explore what a given piece of software should do as well as what it does do.

Consider the following Ruby method that concatenates two words. Simple enough.

def concat_words(word1, word2)
  "#{word1} #{word2}"
end

Here’s a decision table for what actually happens. Is this the desired behavior? Had you considered that one or the other word might be nil? Maybe you’d like to trim any whitespace on the edges of the string? Maybe you’d like to raise an exception when a nil is passed?

Not only does this show that some parts of the system behave in an unexpected way, it might also show that your original specification had a flawed assumption and that it hadn’t considered certain situations.

word1 word2 output
"hello" "world" "hello world"
nil "world" " world"
"hello" nil "hello "
nil nil " "

This is a quick way to get an overview of what the function does (or should do). Some of you may be thinking: how can we automate and verify this? Aren’t these kind of like unit tests? You’re right! Beyond just listing assumptions, we often also want to validate them. More on that topic next week!

Diagramming

When you’re stuck on a programming problem, the best way to get unstuck is often to try to express the problem in a different medium. Switching to a visual medium to express the current situation can often reveal some of your implicit assumptions. So the next time you’re stuck, stop coding and start drawing!

For example, you’re having an issue with a SQL query across several tables so you draw (or generate) an entity-relationship diagram for those tables.

Why is the SQL engine complaining about this query, all I want to do is take data from this table and that table and… oh wait, there’s a join table between them??!! 💡

Entity-relationship diagram showing two tables connected by a join table.

Conclusion

Bugs in a system reflect a miscommunication between the programmer and the machine. These miscommunications are often the result of subtly incorrect assumptions, some of which we may not even be aware that we (or the code) are making. Exposing and validating these assumptions is at the core of all debugging techniques.

Debugging Series

This post is part of our ongoing Debugging Series 2021 and couldn’t have been accomplished without the wonderful insights from interviews with the following people:

Keep tuning in every week for more great debugging tips.