Debugging is hard. Working on a full-stack project makes it even harder.
If you are investigating a bug in your React single page application which sends data to a serverless backend on AWS, saves data to DB and then refreshes the view, there are a lot of moving parts, lots of different components in your architecture. Even reproducing the bug and determining where the bug lies can be a daunting task.
The mantra that I follow when coding (be it implementing new features, or fixing bugs) and that I find myself repeating most of the time when I help junior ( or less junior ) devs is simply this:
Reduce the scope and speed up the feedback loop
What does it mean?
Reduce the scope
- Split the problem into smaller problems
- Focus on individual tiny bits
- Act on the smallest piece of code possible, and directly on it.
Of course you need to spend some time to reproduce and investigate the weird behavior of your application in its entirety. This is difficult but also very interesting - is´s kind of an investigative task.
But once you found what piece of code is responsible - or even if you just have some suspects, just focus on that part. Forget about the rest.
What you need to speed up your productivity is reducing the scope.
Remove the clutter.
Shut down the local server of your react application, forget double-checking the tables on DBeaver. Don't' even think of checking the logs in AWS console anymore.
You know - or you should define - what you expect as input and what you want as output. Write a Unit Test to prove it. All the rest is not important anymore.
Save time, clicks and sanity, focusing only the code that is responsible for the bug.
Speed up the feedback loop
Simply put the feedback loop is the cycle that lets you understand if what you just changed works.
- Reproduce the bug
- Change some code
- Check if the bug is still there
- Change some code again
- Iterate until bug is gone
While doing this you don't want to spend most of the time, waiting for the code to compile/hot-reload, you definitely don't' want to waste time clicking around in your application, filling forms with fake data ( and creating even more rubbish in your backend), you don't want to get bored waiting for the server responding.
The shorter and faster is your feedback loop, the more productive you become. And the more you learn/become proficient at coding - because you spend more time writing code rather than waiting for the feedback loop or trying to reproduce all the steps the bring to a bug.
Don't keep on reloading the app, going to the broken form, fill it, wait for the response from the backend and then read all the logs everywhere...
As I said You know - or you should define - what you expect as input and what you want as output.
Write a unit test if possible, or an integration test if necessary ( if you really need the interaction with the DB for example)
Just have your IDE open, run the code in debug mode, set breakpoints and check what is going on.
(Please, don´t rely on scattering console.logs everywhere!)
Make some changes and run the test again.
Sometimes it does not even have to be a unit test, a simple js file invoking your method, that you execute directly with node ( always with the debugger on and breakpoints set) is often enough.
"But..", you might say, "the data is coming to the lambda from the frontend and if I don´t fill the form every time I don´t know the incoming data.."
Well. Then do that the very last time manually, check in the network console what is being sent, or in the Lambda logs what is being received (the gateway API always wraps the data with some other stuff), save that data into a file or hardcode it in your test and then just feed it directly to the method which is behaving unexpectedly.
So to recap:
- Strive to reduce the scope of your problem,
- Focus on smaller parts
- Find every shortcut allowing you to reproduce quickly your bug and test immediately your changes
- Tighten the feedback loop
Happy debugging!
Photos by Alexei Scutari,Paul Skorupskas and Jason Chen on Unsplash
Top comments (4)
Good advice! I would add just a bit of supplement:
Finding the exact source of the buggy behavior is often harder than fixing the bug once found. Once we find a piece of code taking some odd input and emitting a bad output, we can be trigger-happy to fix that code. Easy! Check if the input is odd, and if so, make the output behave.
But we should stop for a moment, and ask: Is this odd input supposed to arrive here? Why does it even look like that?
It is important to recurse and stop the problem at the roots.
For example, an aggregation behaves bad when receiving duplicate key inputs. Ok, so let's fix it to drop the dups? No, we should ask, why are there duplicate keys? And recurse checking how those were generated.
Then do two things:
My book Programming Without Anxiety has a chapter in progress on debugging with tricks like this and more. For example, it covers preemptive debugging strategies in case you can't interactively reproduce the bug, or time to reproduction is slow.
/Sidenote: I once had to reproduce a bug at Ericsson that only manifested after a call was running for more than 24 hours.. fun times../
That is absolutely a very good point!
By reducing the scope I by no means intended, just solve the symptom! **
A good developer always finds the right spot where to make changes, and often is **further up in the chain. Most of the changes should be made to prevent the bug from happening by ** investigating the root cause.**
By reducing the scope I meant to try to investigate on the smallest possible piece of your infrastructure.
I see too often full-stack dev s- with 2 or 3 screens - with everything open ( DB - Dev Console - multiple IDEs with Frontend code, backend code ) reading console.logs everywhere. when they could at least try to isolate the problem.
Thank you very much for pointing this out.
( I guess I will write a separate post for that. Because it pisses me off when in Code Reviews I find silly quick solutions to bugs that would have been better to be handled somewhere else.
Nice, I would also add to use your local git repo often, checkin often and when you notice a bug you have the option of using
git bisect
to help you to narrow down where it came from. Assuming it was something that was recently introduced.yes, definetely. having a detailed history of atomic commits helps a lot in the long run to find out what piece of code introduced the bug!.
thanx for your comment!