Pythonium

Python, What else ?

Why do developers spend so much time debugging?

Debugging is part of our everyday life as developers. Even when a project is well designed, tested, and documented, there always comes a time when something does not work as expected.

Sometimes, a bug can be fixed in a few seconds. In other cases, we can spend several hours looking for an error that, once found, seems almost ridiculous.

Why does debugging take so much time? And above all, how can we avoid spending too much of our time on it?

The problem is not always where we think

One of the main difficulties with debugging is that the place where the error appears is not necessarily where it was introduced, otherwise it would be too easy ._.

A function can return an incorrect value because another function provided it with incorrect data. An SQL query can fail because of data generated several steps earlier. An interface can display an incorrect result while the real problem is somewhere in the backend.

This is why fixing the first place where we notice strange behavior is not always a good idea. Sometimes we have to go back through the entire execution chain, and if we start from the wrong side, we have to go back through the whole chain again, which can be long and tedious.

Understanding a bug often takes longer than fixing it

This is probably one of the most frustrating things in my life as a developer. We can spend two days finding the source of a bug, then fix the problem with a single line of code that takes us an instant.

The time spent on a bug therefore does not necessarily correspond to the complexity of its fix.

The real work is often understanding what should happen, what is actually happening, when the behavior becomes incorrect, which data is causing the problem, and why that data is incorrect. Once these questions are answered, the fix is sometimes obvious, which can be very frustrating considering how much time we spent getting there.

Error messages do not always tell the whole story

Error messages are extremely useful, but they do not always allow us to immediately understand the cause of the problem.

For example:

Undefined array key "email"

tells us that a key does not exist. But why does it not exist?

The problem could come from an API response, an SQL query, a condition that was not executed, or simply an incorrect assumption in our code.

An error message often tells us where the program noticed the problem, but not necessarily why it happened. This is an important distinction when debugging.

That's why logging errors alone is not enough: we also need to log calls and the actions performed so that we can reconstruct, afterwards, what actually happened. And that's when the detective work can begin...

Data-related bugs are particularly painful

A program can work correctly with some data and fail with other data. This is especially common when working with databases, files, APIs, or data provided by users.

For example:

if ($user['age'] >= 18) {
    // ...
}

might work perfectly until the day age is null, contains an unexpected string, or simply does not exist.

The code has not necessarily changed.

The data has changed.

This is why accurately reproducing the problem is often an essential part of debugging.

And once again, if this data was logged when we traced the actions being performed, we can quickly understand where the problem came from.

Reproducing the bug is sometimes the hardest part

A bug that can easily be reproduced is generally much easier to analyze and fix.

On the other hand, some bugs only appear with certain data, at certain times, when something happens in parallel, or very rarely...

A classic:

“It works on my machine.”

Unfortunately, this does not mean that the code works.

It simply means that we have not yet managed to reproduce the problem in our environment.

There can be many differences between development, test, and production environments: PHP or Node.js versions, installed extensions, environment variables, server configuration, database, cache, permissions, operating system, etc.

Too much code can make debugging harder

The more complex a system is, the harder it can be to determine the source of a problem. A function that performs ten different operations is generally harder to analyze than a function that performs only one. Good practices are not there for no reason!

Let's take a deliberately simplified example:

function processOrder($order)
{
    // validation
    // price calculation
    // applying discounts
    // payment
    // database update
    // sending an email
    // generating a log
}

If something fails in this function, we have to determine which step is responsible. Separating responsibilities can therefore make not only maintenance easier, but also debugging. It is also harder to write tests for functions that do lots of different things.

Logs are our allies

Well, I've already said it several times in the previous paragraphs, but when a problem cannot be easily reproduced, logs become particularly important. A good log should help us understand what happened without requiring a crystal ball.

For example:

Order 1234: payment started
Order 1234: payment provider returned 200
Order 1234: payment confirmed
Order 1234: database update failed

is much more useful than:

Something went wrong

Logs must, however, be designed carefully. Obviously, we should not store passwords, tokens, or other sensitive information in them.

At one point, I was working on a project that had very few logs, and I lost a huge amount of time trying to understand certain bugs. Over time, this probably amounted to weeks of lost time. On top of that, sometimes I only fixed the bugs partially because I did not fully understand them, which made maintenance worse and, above all, damaged the client's confidence in our skills and professionalism...

The debugger can save a huge amount of time

Developers sometimes rely heavily on logs when a debugger could help them understand what is happening much faster. With a debugger, we can execute the program step by step and observe the values of variables at the exact moment when the behavior becomes incorrect.

For example, instead of multiplying:

var_dump($data);

we can place a breakpoint and directly examine the state of the program. This allows us to understand the path actually followed by the code instead of trying to guess it.

At one point, I was a bit too lazy to use the debugger. Big mistake, and a huge waste of time.

Sometimes, I simply use the debugger to validate my code: I check that the different actions are executed in the right order and that the data remains correct throughout the execution. I can no longer do without it, especially when I use prompts to generate code. It allows me to validate and properly understand the generated code.

AI can speed up debugging

Today, AI can also help us analyze certain problems. We can provide it with an error message, a piece of code, or a stack trace and ask what could be causing the problem.

This can be particularly useful when we have been going around in circles for a while. But AI should not replace our understanding of the problem.

A suggestion can be incorrect, too complex, or simply fix the symptom without fixing the cause.

Personally, I find it especially useful as a second pair of eyes. When I am stuck on a problem, asking for another possible explanation can sometimes be enough to get me unstuck.

Some of my colleagues do the opposite: they ask AI first and take over if it cannot find anything at first. But I find that less satisfying when it is the AI that finds the problem...

Conclusion

In the past, we would set off on an adventure to find and fix bugs. We still do it today, and we will probably still be doing it tomorrow — well, I hope so! 😄

Good luck to all the developers heading out on a bug hunt!




Laisser un commentaire