Debugging Software and Coffee
Our coffee machine is currently at the shop for repair, so we're stuck drinking instant. It's not fantastic, but it's really all right in a pinch. In fact, my parents exclusively drink instant because it's what they're used to — seems like a pretty common thing in the UK and is probably a war relic or something (I haven't looked it up).
Instant coffee is really easy to make: you take some coffee granules from a jar, put them in a cup, and add hot water. If you're an uncivilised barbarian, you then add milk and/or sugar. And this is what my fiancée — let's call her Evelien because that's her actual name — was doing on this hot Sunday morning while I was still lazing in bed.
I know that's what she was doing because she woke me up and handed me a cup, saying "try this." Bleary-eyed, I took a sip of what was the second-most (that's another story) disgusting cup of coffee I've ever tasted. It was extremely sour. "No," was all I could muster. To her credit, I received a glass of water in return.
This was Evelien's third attempt at a cup of coffee this morning, and she had me try it because she was starting to think something was wrong with her taste buds. So what had she tried so far?
Making the Coffee
The first cup contained granules from the same jar she'd used (with success) only yesterday, hot water, condensed milk (it's a Dutch thing), and a sweetener tablet. Sour.
After the first cup she checked the condensed milk and thought it smelled a bit off, so tossed that.
The second cup was identical to the first, but this time with brand new milk. Still no good.
For the third cup, she decided it was time to get out a new jar of coffee granules. Other than new instant coffee, this was the same and produced the same sour result (this is the one I tasted).
Back to me, in bed, wishing I'd never woken up. She more or less told me what she'd done. I'd just had a sip of water.
"Is the kettle okay?"
😱
And so it unfolds that Evelien had descaled the kettle yesterday, with vinegar, and completely forgotten about it. Yup, we were drinking coffee made from vinegar in place of water.
A Better Approach
We got to the root of the problem in the end, but it took three cups of coffee, some waste, and a fair bit of time. And our relationship had taken on some serious trust issues.
But what have we learned? How could this be approached better? And why am I talking about making coffee on a software blog?
The main issue is that ingredients were tested as part of the finished coffee. Instead, they could have been tested either in isolation or as part of a gradual process.
The milk was tested in isolation, which is great. The water was not. And what about the water after it had been boiled? After coffee had been added?
In software, it's (usually) easy to open up a CLI and confirm any suspicions. Is your coffee coming out weird? Bust open a CLI, check milk.expired?
, or water.tastes_ok?
regardless of the outcome, you've just narrowed down the potential set of issues.
Another fantastic tool in your box is your debugger. Making coffee is trivial, but building up other systems may not be; a debugger lets you inspect state at each step of the way, letting you home in on the real issue in no time.
def make_coffee(water, coffee_granules, sweetener, milk, kettle)
debugger
# water.tastes_ok? # => true
# next
heated_water = kettle.heat(water)
# heated_water.tastes_ok? # => true
# next
coffee = Coffee.new(coffee_granules, heated_water)
# coffee.tastes_ok? # => true
# next
coffee.add(sweetener)
# coffee.tastes_ok? # => true
# next
coffee.add(milk)
# coffee.tastes_ok? # => false
# We now know that the coffee only turns bad after adding the milk.
# milk.expired? # => true
# And now we know why. If we didn't, we could step into the `Coffee#add` method and see
# if it's doing something unexpected.
end
If you're working through a particularly complex process, throw a couple of breakpoints at milestones and skip between them; you'll narrow down the issue quickly (divide and conquer).
Takeaway
Software engineering boils is about finding and implementing solutions to problems. We solve problems all the time in our day-to-day lives, and the process we use — whether in software or the real world — is much the same.
When something unexpected is happening with your software, you need to go from "the coffee is sour" to "water from the kettle is gross" to "the water is vinegar and needs replacing." If you're trying to determine the problem based on the state of the whole system, you're just making stabs in the dark and it's likely to take a whole lot longer.
Break it down.