Debugging π - Reduce βοΈ or Rebuild π¨?
Choosing Between Removing Dependencies or Incrementally Adding Parts to Solve Issues
As developers, problem-solving is part of the job. Some bugs are easy, others require a fresh perspective after a good nightβs sleep π΄. But when a problem is deeply hidden in your code, isolating it is the first step.
Two approaches can help: removing parts of the system to reveal the issue (via negativa1), or building it up incrementally (via positiva). These methods, known as divide and conquer or incremental debugging, are essential tools for tackling complex problems. Letβs explore how they work using a simple event-driven system as an example.
Say you have an application that receives events, parses them, applies some business logic, writes to a database, and then publishes events.
Via Negativa (Divide and Conquer): Isolate by Removing Dependencies βοΈ
TL;DR: Start with your full system and gradually remove parts to isolate the issue.
Via negativa, often known as divide and conquer, involves starting with your complete system and gradually removing parts. After each step, check if the problem remains, disappears, or changes.
If the problem doesnβt change, the removed part is probably not involved.
If the problem disappears, the removed part is likely the culprit, but continue adding the parts back one by one to confirm.
If the problem changes, the removed part contributes to the issue.
This can be done at the code layer (classes, methods, lines of code) or component level (e.g., database, messaging).
For example, if you suspect the business logic is causing the issue, remove the event consumption component first, then the mapping logic. If the bug remains, youβve eliminated those as causes.
If the problem persists, continue removing components like event publishing or database interactions. Adding logging or unit tests helps pinpoint the failure. Track key points or test individual methods for better visibility.
If the problem disappears after a particular step, repeat the process by focusing on the removed component to isolate the faulty code or functionality.
Via Positiva (Incremental Debugging): Isolate by Adding Parts π¨
TL;DR: Start with a minimal project and gradually add parts until the problem appears.
Via positiva, or incremental debugging, works in reverse. You start with a minimal setup and incrementally add parts of your code until the problem surfaces.
This approach is great when adding a new feature that doesnβt behave as expected. There might be hidden interactions between the existing code and the new addition. For instance, if you're adding tracing and traces arenβt showing up, is the issue with the implementation, or is a dependency pulling in an incompatible version?
In cases like this, via negativa might be impractical. Instead, create a new service with just the essential parts: consume messages, log them, and publish eventsβnothing more. You can run this locally and debug step by step.
Adding parts back incrementally helps identify which component introduces the issue. This method builds complexity in a controlled way, making it easier to pinpoint interactions or dependencies that cause the bug.
By recreating the problem in a simplified environment, you can isolate the root cause and test fixes with minimal interference.
Wrapping Up π
The approach you choose depends on the problem. If youβre troubleshooting an existing system, divide and conquer (removing parts) works well. For new features, incremental debugging (adding parts) helps you build complexity until the issue surfaces.
Both methods make complex problems easier to tackle.
Thanks for reading! I hope this helped you think through new ways to approach problem-solving in code.
I like these philosophical concepts as they are "abstract base classes" for the concrete debugging strategies. While they work well for code, their principles apply to non-code problems, too.