Being an effective web developer when investigating a software issue, locally or in production, requires some detective-like debugging skills. As I've grown as a developer I've not only improved the breadth of my knowledge about various ways software can break in unexpected ways but I've also enhanced my debugging skills over time.
In this article, I share a longer-form version of the above Twitter thread which highlights some of my thoughts on various ways to approach understanding why the computer doesn't appear to be doing what you tell it to do. Computers interpret instructions literally and to resolve a discrepancy between actual and expected functionality you may need to revisit your assumptions!
Software engineers can hold a lot of ill-informed assumptions or falsehoods which can lead to subpar handling of "edge cases" like assuming a user's name is permanent or that a name must by X characters or that a name is only structured as first name, middle name, last name when in reality that is not a universal fact. Check out awesome-falsehood if you'd like to learn more about common falsehoods that engineers believe about things like time, names, addresses, and more.
If an issue seems to be isolated to my local environment and has gone from working to not working without any notable software changes I may try "turning it on and off again" by trying some of the following: rebuilding database, doing a fresh install of node_modules, restarting docker or your computer, etc. But what do you do when just turning it on and off again doesn’t resolve the issue?
Run linting and read any errors Is there a blaring syntax error? Is your code referencing an undefined variable? More times than I care to admit my code wasn't compiling locally because there was a rouge letter 'f' that I somehow added to a random line. This misplaced char is often picked up by my linting but if I do want to manually skim my changes since the app successfully compiled then I will use the VSCode Git Lens Integration to see if anything stands out as off with my changes. Similar to linting errors, if there are other errors in your terminal, browser console, or webpage that are appearing when your app fails to compile or under the unexpected condition they can help guide your search to figure out what is going wrong. When I first was learning how to program, I more so stared 😳 at error messages instead of reading them. If you're still getting comfortable with error messages check out Nicky Meuleman tips on learning to appreciate errors.
Only change one thing at a time. It can be tempting to change everything at once. But try just changing one thing and before you change it think through how that one change will help you confirm or reject your current assumptions of the problem and ultimately help you further your understanding and bring you closer to a potential solution. Automated tests can be helpful to confirm how changes work under different test cases and to ensure as you progress through the problem you don't unknowingly break preexisting test cases that were previously passing.
Confirm all of your assumptions. Consistently reproduce the issue manually or with tests. Under what conditions does the system fail? To better understand an underlying issue you should determine what conditions it behaves unexpectedly in. Using logs, breakpoints, tests, check network calls, etc to sort out when it fails will get you closer to understanding why it's not working as expected. Is your API actually returning the data you expect? Is it an environment-specific issue? Are there missing environment variables?
Lean on tests to automate confirming your assumptions and testing potential solutions Approaching development with a red-green refactor development approach of defining desired functionality in test, implementing code to make the test go from red to green, and then refactoring without having to worry about unknowingly breaking the desired functionality. This approach prevents building out too much of a feature without testing its functionality and if bugs are resolved in a test-driven way then that means that if a similar regression is made in the future in the codebase that there will be a clear failing test warning future developers that something is amiss. Git also has command, bisect which can be used to quickly find a problematic commit, by strategically checking out specific commits from the git history and then based on whether or not that commit is identified as being before or after the software regression was introduced then the next commit is either earlier or later in the git history until the culprit can be identified. Git bisect can be combined with automated testing to make the process of identifying the problematic commit even more efficient.
Walkthrough your code line by line. Try to read it with fresh eyes even if that means taking a 5 min break and revisiting. I'm always amazed how sleeping on a problem that stumped me usually makes a lot more sense in the morning. It's definitely possible for your mind to continue working through a problem even when you are not directly in front of a screen.
Look at the source code of the third-party package. check for open issues. If the error may be related to third-party software look at its source code. I’ve looked at React, misc. packages with types, Webmention, etc on GitHub to better understand their functionality and find relevant open issues. Oftentimes others may have encountered a similar issue.
Google is your best friend A quick search can be a developer’s best friend. Are you using a new function or package? Can you find an example where it's working? If you want to see code that uses the same APIs in context then recommend searching your local repo or https://grep.app to search open source repos.
Phone a friend. I recommend integrating git blame into your development environment I use the Git lens plugin which shows you, directly within files in a code editor, who authored certain file changes and the PR which can be helpful for quickly getting more context regarding decisions by looking at the associated Pull Request or having the opportunity to connect directly with the committer. Pair programming can be an effective way to debug and share knowledge. Similar, to the magic of figuring out the solution to a bug in the shower or overnight there is a phenomenon called rubber ducky debugging in which just explaining the issue to someone else (even an inanimate rubber duck) can help make the solution more obvious.
Once you’ve sorted out a gnarly or even an "obvious" in hindsight bug remember to document your learnings. Even if it’s just a quick note for your future self.