DEV Community

Cover image for How I Debug Faster with These Simple Tricks
Rowsan Ali
Rowsan Ali

Posted on

How I Debug Faster with These Simple Tricks

Debugging is an essential part of the software development process. No matter how experienced you are, you will inevitably encounter bugs in your code. The key to being a productive developer is not just writing code quickly, but also debugging it efficiently. Over the years, I’ve developed a set of simple tricks that have helped me debug faster and more effectively. In this blog post, I’ll share these tricks with you, along with code examples to illustrate how they work.

1. Understand the Problem Before You Start Debugging

Before you dive into debugging, it’s crucial to understand the problem you’re trying to solve. This might seem obvious, but many developers jump straight into the code without fully understanding the issue. Take the time to:

  • Reproduce the Bug: Make sure you can consistently reproduce the bug. If you can’t reproduce it, it’s going to be much harder to fix.
  • Understand the Expected Behavior: Know what the code is supposed to do. This will help you identify where things are going wrong.
  • Gather Information: Collect any error messages, logs, or user reports that can give you clues about the issue.

Example:

Let’s say you have a function that’s supposed to calculate the sum of an array of numbers, but it’s returning the wrong result.

def calculate_sum(numbers):
    total = 0
    for number in numbers:
        total += number
    return total

# Test case
result = calculate_sum([1, 2, 3, 4])
print(result)  # Expected output: 10, but getting 9
Enter fullscreen mode Exit fullscreen mode

Before diving into the code, make sure you understand the expected behavior (sum of [1, 2, 3, 4] should be 10) and that you can consistently reproduce the issue.

2. Use Print Statements Strategically

Print statements are one of the simplest and most effective debugging tools. By strategically placing print statements in your code, you can track the flow of execution and inspect the values of variables at different points.

Example:

Let’s add some print statements to our calculate_sum function to see where things are going wrong.

def calculate_sum(numbers):
    total = 0
    for number in numbers:
        print(f"Adding {number} to total ({total})")
        total += number
    return total

# Test case
result = calculate_sum([1, 2, 3, 4])
print(result)  # Expected output: 10, but getting 9
Enter fullscreen mode Exit fullscreen mode

When you run this code, you’ll see the following output:

Adding 1 to total (0)
Adding 2 to total (1)
Adding 3 to total (3)
Adding 4 to total (6)
9
Enter fullscreen mode Exit fullscreen mode

From the output, you can see that the total is not being updated correctly after the first iteration. This gives you a clue that something is wrong with the way total is being incremented.

3. Use a Debugger

While print statements are useful, they can be cumbersome if you need to inspect many variables or step through complex logic. This is where a debugger comes in handy. Most modern IDEs come with built-in debuggers that allow you to:

  • Set breakpoints to pause execution at specific lines.
  • Step through the code line by line.
  • Inspect the values of variables at any point in time.

Example:

Let’s use the Python debugger (pdb) to debug our calculate_sum function.

import pdb

def calculate_sum(numbers):
    total = 0
    for number in numbers:
        pdb.set_trace()  # Set a breakpoint
        total += number
    return total

# Test case
result = calculate_sum([1, 2, 3, 4])
print(result)
Enter fullscreen mode Exit fullscreen mode

When you run this code, the execution will pause at the breakpoint, and you can inspect the value of total and number at each iteration. This can help you quickly identify where the issue lies.

4. Write Unit Tests

Unit tests are not just for ensuring your code works correctly; they can also be a powerful debugging tool. By writing unit tests for your code, you can isolate specific parts of your code and test them independently. This makes it easier to identify where a bug is occurring.

Example:

Let’s write a unit test for our calculate_sum function using Python’s unittest framework.

import unittest

def calculate_sum(numbers):
    total = 0
    for number in numbers:
        total += number
    return total

class TestCalculateSum(unittest.TestCase):
    def test_calculate_sum(self):
        self.assertEqual(calculate_sum([1, 2, 3, 4]), 10)
        self.assertEqual(calculate_sum([-1, 1]), 0)
        self.assertEqual(calculate_sum([]), 0)

if __name__ == '__main__':
    unittest.main()
Enter fullscreen mode Exit fullscreen mode

When you run this test, you’ll see that the first test case fails, indicating that there’s a bug in the calculate_sum function. This helps you narrow down the issue to the function itself.

5. Check for Common Pitfalls

Many bugs are caused by common mistakes that developers make. Some of these include:

  • Off-by-One Errors: These occur when you iterate one time too many or too few. Always double-check your loop conditions.
  • Null or Undefined Values: Make sure you’re handling cases where variables might be null or undefined.
  • Type Errors: Ensure that you’re using the correct data types. For example, adding a string to a number can lead to unexpected results.

Example:

Let’s revisit our calculate_sum function and check for common pitfalls.

def calculate_sum(numbers):
    total = 0
    for number in numbers:
        if not isinstance(number, (int, float)):
            raise ValueError("All elements in the list must be numbers")
        total += number
    return total

# Test case
result = calculate_sum([1, 2, 3, 4])
print(result)  # Expected output: 10, but getting 9
Enter fullscreen mode Exit fullscreen mode

In this example, we’ve added a check to ensure that all elements in the list are numbers. This can help catch type errors early.

6. Take Breaks and Rubber Duck Debugging

Sometimes, the best way to debug is to step away from the problem for a while. Taking a break can help you clear your mind and come back with a fresh perspective. Another effective technique is rubber duck debugging, where you explain the problem to an inanimate object (like a rubber duck) or a colleague. The act of explaining the problem out loud can often help you see the solution.

7. Use Version Control to Your Advantage

Version control systems like Git can be incredibly helpful when debugging. If you encounter a bug, you can use git bisect to find the exact commit that introduced the bug. This can save you a lot of time, especially in large codebases.

Example:

git bisect start
git bisect bad  # Current commit is bad
git bisect good <commit-hash>  # Last known good commit
Enter fullscreen mode Exit fullscreen mode

Git will then help you narrow down the commit that introduced the bug by checking out different commits and asking you to test them.

8. Leverage Logging

Logging is a more sophisticated alternative to print statements. By adding logging to your code, you can track the flow of execution and the state of your application over time. This is especially useful for debugging issues that occur in production.

Example:

Let’s add logging to our calculate_sum function.

import logging

logging.basicConfig(level=logging.DEBUG)

def calculate_sum(numbers):
    total = 0
    for number in numbers:
        logging.debug(f"Adding {number} to total ({total})")
        total += number
    return total

# Test case
result = calculate_sum([1, 2, 3, 4])
print(result)  # Expected output: 10, but getting 9
Enter fullscreen mode Exit fullscreen mode

When you run this code, you’ll see detailed logs that can help you trace the execution flow and identify where things are going wrong.

Conclusion

Debugging is an art, and like any art, it requires practice and patience. By using these simple tricks—understanding the problem, using print statements and debuggers, writing unit tests, checking for common pitfalls, taking breaks, leveraging version control, and using logging—you can become a more efficient and effective debugger.

Remember, the goal is not just to fix the bug, but to understand why it occurred in the first place. This will help you write better, more robust code in the future.

Happy debugging! 🐞

Top comments (1)

Collapse
 
kelvincode1234 profile image
Precious Kelvin Nwaogu

Useful topic