How to Become a Pro Debugger

Written by pragativerma | Published 2022/09/18
Tech Story Tags: debugging | software-development | software-testing | software-engineering | testing | test-automation | unit-testing | hackernoon-top-story

TLDRDebugging is not something that is taught to anyone at any point in their careers, which leads to the belief that it is very difficult. Despite being a necessary skill for developers, it is hard to understand where to begin as a novice. It is an art that can be acquired by creating and reviewing more and more code over time, but intuition can be developed early on with a few recommendations that we will be discussing today in this article. Here are some simple tips to follow when describing the steps to reproduce a problem: Try to be specific as the more probabilities you mention, the more avenues of investigation are opened.via the TL;DR App

Being a developer means that more than half the time you have to be a debugger, sometimes for your own code, and at other times to help your peers in the code review or in case of pair programming. Despite being a necessary skill for developers, debugging is not something that is taught to anyone at any point in their careers, which leads to the belief that it is very difficult.

Debugging looks difficult, not because of the mechanics of it, but because it is hard to understand where to begin as a novice. Without a doubt, it is an art that can be acquired by creating and reviewing more and more code over time, but the intuition can be developed early on with a few recommendations that we will be discussing today in this article.

Let’s get started.

Reproducing the problem

The worst scenario while debugging any issue is when the bug cannot be reliably reproduced. Thig might happen because of several factors such as race conditions, external systems, or environmental issues that lead to different behavioral patterns on deployed systems or developer stage boxes.

This comes down to both the tester and the developer that the more specific you can be in the manner of reproduction of the bug, the better. Also, once you have found the problem and a way to demonstrate it, ensure that you put all the details about reproducing it as well as understanding and solving it, on a bug tracking system. This will not only help you verify the fix once implemented, but also help the other developers understand the cause and solution of the bug.

Here are some simple tips to follow when describing the steps to reproduce a problem:

  • Try to be specific, as the more probabilities you mention, the more avenues of investigation are opened.
  • In case an input is required, always include some example or sample values. Also, mention the constraints or rules specified for the user input because that could be a probable cause of the problem, which is worth checking.
  • Always specify the environment details - operating system, browser type and browser version. This could be particularly the case for web applications where the different browsers can depict different behaviors.
  • Do include relevant crash reports, logs, annotated screenshots, etc to properly explain the difference between how you expect the application to behave, and how it actually behaves, leaving no chances of misunderstanding to the debugger.

Convert the problem into an automated test

This step isn’t always possible; and sometimes it is possible but not practical or really necessary, however, there can be cases where it is worth implementing. The automated test, in this context, could be anything your development use case is already utilising for testing such as unit test, or an integration test. Wherever possible, unit test is an excellent choice because they are designed to be performed often, so you will know if the problem returns soon.

You may find yourself writing a lot of unit tests that would pass while trying to identify the source of the problem. You may occassionally also find yourself writing unit tests that make no sense outside the current situation, but even so, there’s no harm in having a test that could help you point to the source of the problem. Just because it isn't the present problem doesn't mean it won't find one later - and it also serves as more documentation of how the code should behave. Working unit tests are frequently an excellent approach to determine which layer inside a system is generating the problem.

Refactor your unit tests with the same vigor as you would your production code. If you have a huge unit test that is actually testing a significant piece of code, attempt to split it up into smaller tests. If you're lucky, you'll discover that a huge failing unit test based on the original problem may be split down into multiple passing tests and a single, minor failing test.

Even if you find an issue early on and can solve it in a single line, build a unit test to validate it wherever possible. Making mistakes when correcting code is just as simple as developing it in the first place, and the test-first principles still apply.

Don't expect that things will work like they should

You have to be a little paranoid if you’re debugging any issue to keep yourself going as clearly something somewhere isn’t working as it is meant to, otherwise there won’t be any problem. You need to be open-minded about where the problem can be, and navigate with your knowledge of the system involved.

The major goal here is to stop you from ever claiming, "The problem can't be there." Prove it if you don't believe it. If it appears to be, regardless of how impossible it appears, dig into it more.

Be clear about the purpose of the code

If the purpose of any piece of code is unclear to you, irrespective of whether it may or may not be the cause of the problem, take some time to understand it. You can always put some tests around it, and try to document what it does. If you’re not sure whether the behavior is correct or not, ask for help. Once it is clear, try to document everything you know, whether with a test, XML documentation or some external document.

Fix one problem at a time

If a piece of code is exceptionally bad, you may end up discovering other issues while debugging the original one. In such instance, it is critical to decide which problem has to be tackled first and then to solely focus on that problem.

In a perfect world, you would solve one issue, verify your code into version control, then fix the next, and so on. This makes it straightforward to determine which changes in code led to which changes in behaviour later on, which is critical when a fix for one problem turns out to break something else.

Make your code help you debug

Logs can be incredibly useful in debugging. They can give you information from the ground where debuggers might not be helpful. However, logging, much like debugging, is also a careful art. If you don’t do it correctly, it might not be able to help you the best way possible. So, whenever an exception is thrown in the code, make it a point to not just log the message of the exception but also the stack trace.

Exceptions should essentially never be swallowed silently without being logged. They can sometimes be the easiest way of verifying user input or configuration settings. In these situations, logging can be truly your friend.

Similarly, make your code resistant to bad input. The earlier a bad input is detected, the easier it is to find the problem. It might be difficult to track down the root of a problem if you only obtain an expectation after a value has been handled by several methods. Contracts could be helpful in defining the constraints here, otherwise, you should be vigilant and add validations if you suspect a problem in the future.

Learn about your debugger

Just like a Jedi can’t be great without his Light Saber, you can’t be a great developer without knowing about your debugger’s capabilities. Modern debuggers have a lot of features which are mostly underused because developers are unaware of them. For example, conditional breakpoints or user-defined representation of data structures can make the difference between a debugging session that lasts for a whole day or the one which just lasts for 10 minutes.

You don’t need to know everything by heart, but a decent idea of what your debugger is capable of, and a good knowledge of shortcut keys involved for the basic functionalities such as step over, step into, set breakpoint and resume execution are important.

Involve the people

Debugging is difficult as it may get demoralising, and working with a poorly documented labyrinth of code can quickly lead to physical exhaustion. Consider this: if you're stuck, take a break, go for a stroll, eat a coffee, a chocolate bar - whatever helps you.

Don't be embarrassed if you need to include someone else - if the behaviour of the code appears implausible, or if you just don't know where to look, ask. Keep an eye on what the other person is doing, both to learn from them and to warn them if they appear to be heading into a blind alley you've previously observed. Let them know what you observed, and what you think, a discussion might bring up a hint.

Test to the END

When you think you've solved the problem, do all you can to test it (within reason). Obviously, unit tests should be your first port of call - the unit tests you introduced to isolate the problem should now pass, but so should all of the other tests.

A high-level problem is frequently caused by numerous low-level ones - it's easy to discover the first low-level one, correct it, and then assume the high-level problem will go away. Sometimes the opposite occurs: the high-level behaviour deteriorates as a result of a more significant low-level issue. In that case, you will need to keep fixing and then testing until you see that everything is working correctly.

Audit for potential errors

Problems frequently occur in groups. For example, if you discover that someone forgot to do sufficient escaping/encoding on user input in one instance, the identical issue may arise in another. It's significantly less expensive to run a rapid audit of possible problems all at once than it is to leave each issue to be raised as a distinct problem, maybe involving other engineers conducting the same investigations you've just completed.

Consider the consequences of your patch in a similar vein. Will it cause an issue somewhere else that may not be fixable in the short term? Will it have an impact on patching or upgrades? Is it a significant repair at this stage in your product's lifecycle? It's usually always worth it to figure out what's wrong, but occasionally it's safer to leave the code broken - potentially with a "band-aid" repair that tackles the symptoms but not the cause - and leave the complete cure for the next release. This is not mean that you should leave the bug report as is.

If you have a suggestion for a repair, try creating a patch file and adding it to the bug report (making it clear which versions of which files need patching, of course). At the absolute least, provide a detailed explanation of your research in the bug report to avoid wasting time later. A bug status of "Solve next release" (or whatever your equivalent is) should indicate just that, not "We're unlikely to ever really fix this, but we'll delay it one release at a time."

Conclusion

These recommendations can’t make you a pro debugger in a day or two, but it will surely help you get started in the right direction if followed with patience and good practice. High quality code doesn't happen by accident - it takes patience and precision.

That was all for this article. Let me know what you think about the above tips for debugging and feel free to add your approach, tips and tricks for becoming a pro debugger in the comments below.

Keep reading!


Written by pragativerma | Software Development Editor @HackerNoon | ex - SDE @BrightMoney | Full Stack Web Developer
Published by HackerNoon on 2022/09/18