Debugging is a Story
Today I want to talk about a way I think about debugging: as a story that we tell to ourselves and each other.
We often face bugs without much information. I'm talking about errors like this:
$ npm run build
The 'compilation' argument must be an instance of Compilation
What? This error comes from my work, and I caused it. I pushed a change to our codebase that removed a flag hiding errors. When the errors were exposed, we had to upgrade dependencies. Along the way, I broke the build, and no one knew it for a while.
First step: increase the verbosity! I quickly learned I can't make npm run build
more verbose– the script swallows errors. Welp!
I tried Googling the error and got back some breadcrumbs: Stack Overflow answers, and GitHub issues. They all discussed the same thing: this kind of error happens when an application uses two versions of Webpack.
Let's start telling a story:
"We were using a flag that hid warnings, and we removed it and upgraded dependencies."
So far, so good! Updating dependencies is good. It usually makes the software more secure. It improves the developer experience. It gives me features. I like this story.
While doing something good, something bad happened, which is that the build broke. This is a subtle distinction: some problems are good. The build being broken because we moved forward is a good problem.
There's more to the story! We had a build step that wasn't tested in CI or CD, so we didn't know it was broken immediately. That's a separate problem in our workflow that we found, which is good!
Let's add to our story:
"We were using a flag that hid warnings, and we removed it and upgraded dependencies, and the build broke."
Our problem is still frustrating and needs to be fixed. And we've learned that this error message almost certainly comes from competing versions of Webpack.
What's next? We decide to run a command that will tell us which dependencies are asking for Webpack, along with which versions.
$ npm list webpack
Our output confirms our hunch: we have a few dependencies using Webpack 5, and a few using Webpack 4. Now our story is coming together:
"We were using a flag that hid warnings, and we removed it and upgraded dependencies, and the build broke because we're now running two versions of Webpack."
What should we do with this information? We could revert my changes. We could hide the error. We could downgrade the libraries asking for Webpack 5. Or, we could upgrade the libraries asking for Webpack 4 to ones asking for Webpack 5. Let's try each story ending.
"...we reverted the changes (losing work and time)."
"...we hid the error (ignoring the information it was providing)."
"...we downgraded libraries (moving backward to older, unmaintained code)."
"...we upgraded our libraries to ones that use Webpack 5."
The last ending is my favorite. We upgrade the libraries, check the release notes, smoke test, and push the code to unblock our teammates.
Here's our full story:
"We were using a flag that hid warnings, and we removed it and upgraded dependencies, and the build broke because we're now running two versions of Webpack. So we upgraded our libraries to ones that use the latest Webpack."
I like this approach for a couple of reasons.
One is that any debugging session can have a lot of outcomes, and some are better than others. If we don't consider the story, we might jump to an inferior ending and never look back.
Second, we have to explain technical choices. We must be able to tell stories like this.
And lastly, I want to tell stories that I'm proud of. This story makes sense to me, makes sense to my team, and it will make sense in six months.