The hidden trap of debugger
Tags: productivity, debugging, feedbackloop, programming
Published: 2021-12-14 19:28:41

> "Debugging is like being the detective in a crime movie where you are also the murderer." - Filipe Fortes

## How it started: a false illusion
When i had my first programming experience i was not aware of the possibility of using a debugger.
It happened that i had to understand why my software was not working as expected , so the only way i had to understand what was going on was to add some print statements all around the code.
Later I learnt that i can use the debugger as a tool to inspect variables and understand the status of my software in a specific time.
I thought 
> "I can troubleshoot without adding new code!! 
I will be faster than before!"

## How is going
After years working on many and more complex projects  i almost totally change my mind, and realised that actually _**the debugger slowed me down!**_
To be clear i still think that in few specific simple cases it is worth to leverage the debugger but in complex cases i think there are better alternatives.
What happened ? 
I was thinking that _debugger was the only way to troubleshoot issues_ so i used it all the times.
If i come back to the my most stressful working times almost all of them were moment in which i was trying to understand the code ,spending hours in long debugging sessions inspecting tons of line of code variables and stack trace.
Can you remember your most stressful working time ? 
Can you remember if in those moment you were in long debugging sessions ? 
Probably you were trying to [escape the legacy code matrix](https://www.slideshare.net/MarioRusso6/escape-the-legacy-code-matrix-vimercate) and you were stuck on the first phase: the _understanding_ phase


## Where is the trap ?

I will share now why in my opinion in complex use cases using the debugger actually slow us down and increase our stress and cognitive load  

**1. Give up to learn the code** 
Using the debugger is like using a navigator for your car. It actually helps you but sometimes for some reason it dint work and when it happens you are totally lost because you don't know the path. 
Jumping soon to debugger prevent you to learn the code and to improve your reading and reverse engineering skills.
Those are skill that you can acquire only reading more and more code written by someone else. The more you do the more you get better to understand it. An remember there are cases in which you cannot rely use the debugger so it is better to have an alternative.

**2. Manual actions** 
Inspecting a lot of lines of code and variables require manual and very repetitive action like put breakpoint , enter inside function , go to next line , inspect variable, add watches etc..
Manual action is _much slower_ then automatic action that could performed by computers.
Keeping in mind a lot of variable values and application state _require high cognitive load and energy_.

**3. Repetitive actions** 
Imagine that you have the following type hierarchy

```kotlin
data class University(val departments: List<Department>)

data class Department(val courses: List<Course>)

data class Course(val students: List<Student>)

data class Student(val name: String, val country: Country)

enum class Country {
    ITALY,
    SPAIN,
    FRANCE
}
```
and you need to check more than one time if an university has students from Italy
Every time you need to inspect the country of student in university you need to do at least 5 action like five clicks , inspect variable take notes or remember their values 
If you have to it 10 times , you will have to do 50 click or debugger inspections!.

An alternative approach could be write a function like 

```kotlin
fun logItalianPresenceInUniversity(university: University)
```

that prints what you want to check. Few additional code to write and just 1 click to execute and check the result.

**3. Lack of trust in tests** 
Someone said 

> "A bug is just a missing test"

The purpose of our tests is to help us to define the behaviour of our system and help us to find issues.
An alternative way to debugging to detect an issue is to _reproduce it by adding a new test_. 
Now if we found a bug thanks to debugger we can call in trap to think "_test suite did not help me, so i will do the fix and forget the tests_".
So we may enter in this dangerous loop 

![debugger loop fix without tests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gidqt1y9ws9gp7v9lotw.png)
 
  
  

## A sample case: microservice with many data transformations 

Imagine a service exposing some functionality to the user that need to call other services to fulfil the request
In complex cases there are many data adaptation between the [controller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) and the call to the other services

![A service with many adaptation steps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7n022wqz85lmo2ivwovt.png)

Now let's suppose that there is a bug in that service and some information that arrives from the external service is not sent back to the controller and the user.
Your task is to spot it and fix it and you need to deep dive in many adaptation layers.
Which options you have ?

**1: Understand and document every little detail of the code**
The safest option is to read carefully the code trying to understand it , taking notes and spot the missing point.
This option is good but may require a lot of time and effort to deep dive in the code structure. In legacy systems or when you don't have much time _it can become an endless process_. On other hand it on long term it _improve your skills to read the code_.

**2: Use the application with debugger**
We could put a breakpoint on controller and on client and use the application. 
In our case having a lot of adaptation layers this will be take a lot of time with debugging session and so big effort.
For non trivial cases this approach is probably the worst one.

**3: Use the application with probes in code**
Here you can guess what is the idea.
You can write some small probe functions to automatically check what would manually ask the debugger to do. Remember the `logItalianPresenceInUniversity` function described above ? .

My personal approach in this cases is 
_1. identify the start and end of code section to check_
in this case the controller and the client 
_2. Automatize checks by writing probes functions_ 
 Example add function that print if a data structure contains the data that i am looking for 
_3. Put probes in code and create a [git patch](https://devconnected.com/how-to-create-and-apply-git-patch-files/) for local changes_ 
_4. Run the application and check the probes_
_5. Repeat step 3 until issue is found_
 A [binary search algorithm](https://en.wikipedia.org/wiki/Binary_search_algorithm#Procedure) can limit the tries 
_6. Rollback local changes when done_ 


In complex scenario i find this approach _faster_ , incremental and it require less cognitive load.  

## Issue is found now what?
Regardless how i found my preference is: 

1. highlight the issue by writing a new test and watching it fail***
2. Fix the text


## Conclusion
When we have to find issues in code we have many option on the strategy to follow.
Using the debugger is only one of them and in my opinion in some cases is not the best approach.
Having more than one option we can choose the one that fit more our case.

*** _Writing a new test can be hard in some cases you may need to break dependencies to isolate the section of code as described in [working effectively with legacy code](https://www.amazon.it/Working-Effectively-Legacy-Code-English-ebook/dp/B005OYHF0A)_