DEV Community

Cover image for Debugging rules: Change one thing at a time
Rajasegar Chandran
Rajasegar Chandran

Posted on

Debugging rules: Change one thing at a time

Altering elements individually poses its own set of challenges. When modifications occur in pairs, the potential arises for subtle errors that closely resemble the initial issues. This leaves you in a perplexing situation, unsure whether the correction wasn't applied, if the correction inadvertently introduced a similar problem, or if the original error persists along with a comparable new one. To navigate this complexity, adhere to simplicity: implement just one change at a time.

Opt for a focused approach of making singular adjustments. Discard the notion of a shotgun strategy; instead, equip yourself with a precise rifle. This method proves more effective in resolving bugs, as indiscriminate alterations can disrupt otherwise functional components.

Moreover, pinpointing the exact failure allows you to address only that specific issue. Essentially, if you believe a wide-ranging approach is necessary, the underlying problem likely lies in the lack of clarity regarding the target.

In numerous instances, the inclination to modify various components within the system arises with the aim of assessing their impact on the issue at hand. However, this tendency often serves as a red flag, indicating a reliance on guesswork rather than leveraging instrumentation effectively to comprehend the underlying dynamics. Instead of diligently observing the natural occurrence of the failure, there's a shift towards altering conditions, potentially concealing the initial problem and potentially giving rise to additional issues.

It's more effective to remember to do
something than to remember not to do something.

Isolate the key factor

The essence of effective debugging lies in pinpointing the critical factor by narrowing the focus to a specific section of code where a potential issue may be situated. This targeted approach empowers developers to efficiently recognize and address problems, leading to time and effort savings. By honing in on a particular segment, be it a function or module, the debugging process gains precision and effectiveness.

Image description

For a robust technique to zero in on the suspicious region of the code, systematically eliminate portions of the program and observe if the error persists. If the issue disappears, it indicates that the problem lies in the removed part; if it persists, the problem resides in the retained portion.

Consider whether this exhibits a familiar pattern. Recognizing the phrase "I've seen that before" often marks the inception of understanding, if not the complete solution. Common bugs tend to exhibit distinctive signatures.

Change one test at a time

Occasionally, altering the test sequence or adjusting certain operational parameters can induce a problem to manifest more consistently, offering a clearer view of the failure and providing valuable insights into the underlying issues. Nevertheless, it remains crucial to adhere to the principle of modifying only one element at a time. This ensures precise identification of the parameter responsible for the observed effect. If a modification appears ineffective, promptly revert it to its original state.

Image description

Compare it with a good one

Once you possess a method to induce a system to either fail or remain functional, even if the occurrence is sporadic, you find yourself in an excellent position to function as a differencing engine.

Be a differencing engine! See the world! Or, at least, see the differences!

By examining two scenarios, one experiencing failure and the other functioning correctly, compare various aspects such as scope traces, code traces, debug output, and status windows or any other instrumented elements.

Image description

If a multitude of code changes occurred between the two tests or if distinct scenarios were set up, comparing the tests can become challenging. Constantly addressing differences unrelated to the bug might obscure the identification of the actual issue. It is essential to minimize disparities between the two traces to isolate the bug. Aim to obtain logs from the same machine during consecutive attempts, avoiding variations like different machines, software, parameters, user input, days, or environments.

This doesn't imply that you shouldn't instrument elements unrelated to the bug. Since the relevance of certain aspects to the bug is not yet clear, comprehensive instrumentation is crucial. Even if an aspect is unrelated, it will manifest consistently in both logs, allowing you to swiftly bypass irrelevant data during analysis.

Identifying problematic elements isn't a skill easily imparted to beginners or automatable through programming. What you seek is ever-changing and distinct from previous instances. Navigating through irrelevant disparities induced by timing or other factors requires a significant level of knowledge and intelligence. This level of understanding surpasses the capabilities of a novice, and the required discernment goes beyond the capabilities of software.

When confronted with an extensive and intricate log, there may be a temptation to focus solely on suspect areas, which is acceptable if an issue is promptly discovered. However, in cases where no immediate revelation occurs, be prepared to examine the entire log, as the location of pertinent differences remains uncertain.

If the bad ones all have something that the good ones don't, you are onto the problem.

Examine the most recent change

Identifying the most recent alteration is crucial. When adhering to the practice of modifying only one aspect at a time during the evolution of a program, the bug is likely either within the new code or brought to light by it. Scrutinizing recent changes is instrumental in localizing the issue. If the bug surfaces in the new version but not in the old one, it implicates the new code as part of the problem.

At times, the distinction between a functional system and a malfunctioning one can be traced back to a design alteration. In such cases, a once-reliable system starts exhibiting failures. Pinpointing the version that initially triggers the failure, even if it entails testing progressively older versions until the issue dissipates, is beneficial. Once the problem disappears, progress to the subsequent version and confirm the recurrence of the failure. This process effectively narrows down the problem to alterations between those two versions, providing a focused perspective on the issue, assuming the change wasn't a complete overhaul of the system.

Image description

Typically, new designs undergo testing before being shipped due to the common occurrence of faults in their initial iterations. Compatibility issues often arise when a new design in one section does not align well with another section that was functioning perfectly.

However, some cases prove challenging. Occasionally, a longstanding issue only manifests itself when other changes occur. The introduction of new code or a hardware revision may create conditions causing a previously reliable subsystem to fail. It's akin to discovering a hole in the subsystem that was always there, but you never ventured close enough to fall through it before. While fixing the immediate bug leading to the hole might be tempting and sometimes necessary for short-term solutions, the ultimate goal is to address and seal the underlying issue.

When confronted with a perplexing new error, the root cause is often linked to recently altered code, whether it's entirely new or modifications to existing code. If unable to identify a defect, running an older version of the program can help determine whether the error persists. If it doesn't, it indicates that the error lies in the new version or stems from an interaction with the changes made in the new version.

Scrutinize the differences between the old and new versions

Examine the version control log to identify recent code modifications. If this is not feasible, employ a differential tool to compare alterations between the previous functional source code and the current malfunctioning source code.

Seeking predictability in your routine is essential. Eliminate modifications that did not yield the anticipated results, as they likely introduced unexpected consequences.

Top comments (0)