Ever Spent an entire week to find out where the bug is?
Ever Wondered how this bug was never caught during the development phase?
Then this post will definitely be useful for you 😃
This post will explain how to find the source of a bug in a code, and also about best practises while writing code 😃
What type of bug is that hard to find?
Let’s say that the code has 100000 lines of code.
Now the Code does not throw any error when it’s run. So that’s good 😃. No-one likes errors right?
Now one of your customers reaches out to your development team and says that they are not able to perform some action in your application.
Now you will need to find out why the code is doing this. But as I had already mentioned the code does not throw any errors.
Now, the question is how do you find out what went wrong in 100000 lines of code 😕
An error doesn’t look so bad now right since it at least gives you some information on what might be wrong 😃
Now, how do you find this bug?
Debugging to the rescue 👍
Debugging
What is Debugging?
Well, as the word says it is De — Bugging. Debugging is the process, where you go over the code to find out where the bug is.
What tool do you use to debug?
You guessed it right. It’s a debugger 😃
Depending on the language the code is in, you first need to choose the right debugger tool. If you are using Eclipse, it automatically comes with java debugger. If you are working with javascript, you can use the debugger which comes with any web browser and so on.
What exactly do you do while debugging?
Using the debugger, you can set checkpoints in your code and then run the code in debugging mode.
Let’s say you set a checkpoint in line 10 of the code. Now when you run the code, the code will stop running and pause at line 10.
Now at this state, you can do things like inspect the variables in the code and see if anything seems odd. You can check what values the variables contain. You can verify if the content of an array or object is proper and so on.
If any variable has a weird value, then you have a possible suspect 😃. Using this information, you can set checkpoints wherever that variable is present and keep repeating this process until you find the true source of the bug 😃
Debugging seems pretty easy, what’s the catch?
The catch is you have 100000 lines of code. Where do you put the initial checkpoints?
It’s possible that the code was written by multiple developers over the years and no single person knows the whole codebase. So how can you know where to put the initial checkpoints?
Well, the truth is this
In order to debug the code easily, the code must be written in such a way that
it is debuggable in the first place.
In order to debug the code, you will need to understand what the various parts of the code are doing on a very high level.
But to understand the code, the code must be written keeping some best practices in mind. I will be mentioning some of the best practices here.
Making the code modular
Simplicity is the Ultimate Sophistication — Leonardo Da Vinci
Imagine having a single file with the entire 100000 lines of code. It is impossible to read such a code.
Instead, it is a good practise to break up the code into multiple modules so that each module does a specific task.
This idea can be expanded as well. First, the application can be divided into a number of bigger modules, and each bigger module can be divided into a number of smaller modules.
For Example, Let’s say you are building an e-commerce site. The application can be divided into bigger modules as follows.
- Login/signup page
- The homepage
- A shopping cart
- Search option
- Recommendation option and so on
These are bigger modules since they perform a big task. This can be broken into a number of smaller modules
For example, Signup page can be broken down into
- A module for reading user input
- A module for validating user input
- A module to check if the username already exists in system
- A module to check if password is strong and so on.
Dividing the code in this way makes it more readable, and helps make the code more debuggable.
Right naming conventions
Let’s take the below code as an example
function abcd(c) {
//Some main logic here
return z;
}
We have no clue what the above code is trying to do since it does not have a proper naming convention. Let us rewrite the code
function validateUsername(username){
//Some main logic here
return isValid;
}
This code makes more sense than the previous one. This code is trying to validate the username.
Having proper naming conventions makes the code easier to read. This, in turn, makes it easier to debug the code.
Documentation
So you have finished writing your code and everything is working. Great 😃
Now it’s time to write documentation 😕
I know, I know. You may be thinking “Hey the code is working, why document it”. Well, documentation is what ensures that others can understand what code you have written.
In fact, if you look at your own code after 6 months, you will have no clue what it is doing without the right documentation 😃
Consider the code below.
function cleanData(data){
//cleaning logic
return cleanData;
}
In the code above, naming convention is good. But what is the above code trying to clean?.
/**
* Function to clean input data
*
* 1. If any of the rows have null,
* replace with 0
* 2. Ensure that 'id' value of a row
* is not null. If it is, then
* skip row
*
* @param {Object} data : Input Data.
* @return {Object} : Returns an object
* which contains clean
* data.
*
*/
function cleanData(data){
//cleaning logic
return cleanData;
}
The code above has documentation. Now it is somewhat clear what cleanData function is doing( This documentation can be made better). You may feel the documentation is bigger than the code itself here 😃. For smaller functions, you can use a simple form of documentation. But for bigger functions, a proper documentation is needed.
I know it’s an extra effort to write documentation. But you will appreciate documentation in the long run 😃
Documentation helps in debugging because it helps in understanding what a piece of code does, without going through the code in depth.
Unit Tests
For example, consider the following code.
function sum(num1, num2){
return num1+num2;
}
This function calculates the sum of 2 numbers and it is working fine.
Let’s say someone changes the code above to the following by mistake.
function sum(num1, num2){
return num1*num2;
}
Now the code is wrong since it is returning num1*num2
rather than num1+num2
.
Unit tests catch such issues automatically without someone manually reviewing the code.
So the Unit test is a piece of code which will test the sum function by giving different values for num1 and num2 and see if the right output is coming out.
Unit tests ensure that such minor issues are caught during the development phase itself. If these issues are not caught during development, then they can pile up and create a major bug in Production. So it is always better to write Unit tests. 😃
Top comments (7)
That's just one tool in my toolbox in order to debug a bug. Sometimes just looking at the nature of the bug and at the code will be enough. Other times print statements will be faster and simpler. Yet other times, if it's really not evident eventually I have to go to the debugger.
Agree with you on the print statements bit. Simpler bugs can easily be caught that way.
Debugger as you mentioned is more useful for complex and weird bugs.
yup. Unit tests can catch bugs instantly in case the code is broken. This way a lot of bugs do not end up in production
Have debugged concurrent issues a few times and as you said, extensive logging was what we did to figure out the issue eventually. :).
I think using multiple other tools for concurrency like dtrace, and other distributed tracability logging framework to trace the end to end request/response flow. Things like ELK stacks also help in log discovery
Next time will give these tools a shot :)
good point. Is there any best practise for debugging code with high concurrency?