When I was a junior developer, I starved for knowledge about the ways software engineers work. In my stories, I want to depict closely how this happens during tangible actions. Even more, I want to describe the ways that give better results than the most evident ones.
The approaches I describe do not have to be challenging to implement. The ongoing debugging process is almost apparent. Nevertheless, it adds more structure and boosts your debugging power.
As in my previous stories, we go through a practical example to demonstrate the subject in action. I like the combination of succinct description and practical examples for two reasons:
I plan to be even more helpful to the software engineering community in this story. I'll find a bug in one of the open-source GitHub repositories and try to fix it. I never did any significant open-source software fix before except for the documentation update. I hope my first experience won't break anything important.
I also didn't know how to find a repository where my help would be valuable. So I decided to navigate to the Explore and then to trending Clojure repositories. I like Clojure, but it is not the language I write on my job. From time to time, I try to find additional practice out of it. I wander through a few repositories and then decide to find something in the Metabase project. I like how they document things, and it's easy to discover bugs here.
We've already gone a little too far into the action. Let's step back to discuss the instruments I want to propose. They are:
Hmm, why are these instruments the sharpest ones? This question is easy to answer. I guess you once experienced paper cuts or even pen pricks. Regarding the brain, there is a "sharp-minded" word.
The paragraph of humor came to an end. Let's discuss more intriguing questions. Why do we want physical paper and pen? Aren't they inefficient?
Software engineers love automation and optimization. The shiny golden dream is to relax in the pool with a glass of refreshing drink. At the same time, your computer does all the necessary things to earn you money for a relaxed life. I'm happy for you if you are already there. Usually, we are somewhere on the road to this place.
Some engineers also love purity: excellent testability and predictability, no side effects.
Let's see where these beautiful ideas work against us.
First, let's discuss optimization. Who doesn't like when something perfectly fits? Imagine you have jeans, and they are optimized for your size. No single superfluous pinch of fabric is present — no space between you and your jeans; it’s a beautiful mathematical balance. The problems start when you try to put them on. If there is no extra space and fabric, you cannot move your foot through the place prepared for the upper part of your ankle. It is usually thinner.
Several years ago, I read another example about optimization flaws in the book called "Hard and soft mathematical models" (Russian). Vladimir Arnold, the famous Russian mathematician, is the author of it. The case about fishery demonstrated the situation where there was just enough fish left to support the population but no more. In this case, a minor casualty can bring the fish population to extinction. The optimum is too fragile.
Second, let's discuss automation. You take something imperfect or even dull and let the computer do it. Sounds cool! But what if we do not need to do this at all? What if we can drop these activities and win? At the beginning of this year, I listened to the "Lean Thinking" book by James Womack and Daniel Jones. It describes the lean methodology applied to manufacturing. The idea of lean is that you find imperfections on the path of value creation and eliminate them. Imagine that you wrote a program that sets these imperfections in the concrete. Sounds not that cool. What astounded me was removing complex manufacturing management software after cleaning much Muda (waste) from the process. You don't need complex solutions when you manage to mitigate complexity.
Third, and last, let's discuss side effects. We don't like them in our functions. We want to give them extra emphasis to prevent mistakes. In contrast with refined abstractions, life is not pure. We do have side effects, and some of them are even positive ones.
Writing by hand is not that fast as writing using a keyboard. I tried to write the "speed of writing" phrase on paper, which took me 8 seconds. I am not a native speaker and might be slow in it. The same exercise with the keyboard took me 3 seconds. When I write a sentence, I usually know at least a part of it that I want to put into words. When being slow and writing by hand, I can free my active usage of the left brain hemisphere and relax. This condition is not a pure relaxation with no action taken. It's the time of our right hemisphere performance.
Let's see what Andy Hunt writes about the competition between our hemispheres called CPUs in this paragraph:
Notice that both CPUs share the bus to the memory core; only one CPU can access the memory banks at a time. That means if CPU #1 is hogging the bus, CPU #2 can't get at memory to perform searches. Similarly, if CPU #2 is cranking away on a high-priority search, CPU #1 cannot get at memory either. They interfere with each other.
The quotations above and below are from the "Pragmatic Thinking and Learning" book.
Do you remember the new music you listen to when actively being involved in detailed programming activities? I don't, and we now can understand why.
And why do we want to give the ruling to our right part of the brain? See the answer in the following quote:
You need both: R-mode is critical for intuition, problem-solving, and creativity. L-mode gives you the power to work through the details and make it happen. Each mode contributes to your mental engine, and for best performance, you need these two modes to work together.
It's great that you are concentrated and disciplined to implement your hypotheses. Don't forget to allow your brain to prepare them for you. Gather the details carefully researching, and let them synthesize while seemingly relaxed.
What a great power we get from almost invisible natural obstacles! What an unwanted side of efficiency we've just seen.
After such an extensive introduction, let's finally look at the structure of the problem-solving paper.
Problem You Are Trying to Solve
First hypothesis
First subhypothesis
Notes
Subhypothesis testing results
Notes
Second subhypothesis
Subhypothesis testing results
Second hypothesis
Hypothesis testing results
...
Do not treat this structure as a rigid one. Think of it as gentle guidance. What you need is to see the destination — it's in the header. Also, you need to observe the path you decided to take. You can return to it if you've accidentally left. Or you can explicitly state that you are on a new one.
As before, let's discuss why do we need it? Aren't we smart enough to know where we go?
We are smart for sure. What we also are is that we can become stuck. Do you remember waking up from your chair without results after several hours of debugging? Do you also remember that you couldn't state what you were doing? We are going to handle this. But first, let's look at some alarming evidence of human nature from the "On Trails" book by Robert Moor:
...on average, people who are lost, without external navigational cues, will typically not travel farther than one hundred meters from their starting point, regardless of how long they walk. A horrifying thought: On a cloudy day, in tall woods, with no other cues and no compass, a person will not travel more than the length of a football field in any one direction.
In the quotation above, the one who's lost has its eyes to analyze what's around. When debugging, much of sightseeing happens in our heads. And your eyes look in the opposite direction. So you are both lost and blind.
I think that the worst problem I solved with this method is worth mentioning here. I once faced the problem of non-working legacy front-end microservices in any browser except Google Chrome. My objective was to make it run in Firefox. The complexity of the situation was spiraling because of the following factors:
I made it, in the end, using the problem-solving paper described above. I think it took me about 3 or 4 days of debugging, making hypotheses, and verifying them.
The problem was the insensibility of Gulp to duplicating transitive links to the moment.js library and its plugin: moment-timezone. The legacy application used the deprecated HTML modules functionality: there were around 20 imports in total. There also were two versions of both library and plugin. In Chrome, the correct version was in use, and in Firefox happened the version mismatch. Adding few extra build steps brought the application in Firefox to the working state.
At the beginning of this article, I mentioned that I like Clojure. I was brave enough to find the project which uses this language for the back-end. However, I decided not to leave my comfort zone too far and chose the front-end problems. I decided to take the one which is not that important to become overtaken by someone else. On the other hand, it shouldn't be too old to become irrelevant. I've chosen the "Saved Questions search input is super jumpy" Github issue.
The list of items becomes filtered on typing, and the filtering input is in the same scrolling block. For every character entered, the scroll moves to an unexpected place, thus disorienting a user.
I am for the first time in this codebase, and everything is unknown to me. I would quickly find the place requiring the fix and then begin applying the method I describe. At least I thought that.
After half an hour of wandering, I found myself stuck. The application is not that big, but where is the component I am trying to find? It looks like I need to take my paper and pen and start the guided investigation.
After ~6 hours of searching, I discovered what I needed. It turned out to be the AccordionList
component requiring a fix.
By the way, a good bud description could save me hours here. Because of this, I am a great fan of the Definition of Ready.
I even thought of a better way to structure the notes, and I'll present it later. Here are the raw records I've taken during the investigation.
I've come through the following hypotheses during the discovery.
I need to find the input or the "Saved Search" list item to discover the place where the problem arises.
It turned out that there exist both the dedicated component and several ad hoc search inputs implementations. None of the places containing the "Saved Questions" text were convincing enough, or not they were discoverable through the UI.
Did someone remove this list at some point in the application development?
I discovered no deletion of an item containing the "Saved Questions" text after creating the GitHub issue. Furthermore, someone has added a new component containing this line.
There is something about Stats in the bug description, but I can't see something similar. Maybe these words are about Metrics, and the component is there?
I've investigated the Metrics part of the application and didn't find anything close.
I have a feeling of seeing this list somewhere, but where? Maybe in the administrative part of the application?
No, I don't see anything familiar here.
Maybe I should look once again at the search inputs in code?
Hmm, I see some conditional logic for adding the search input to the AccordionListCell
component. The "accordion" word makes me think that it's something collapsable, like the component I can see on the GIF in the issue description.
And after few more thoughts... Got it!
The AccordionList
component is the place I was searching. When I discovered it, I'm perplexed that it took so long — you meet this component one attracting click away from the main page.
If you sometimes feel puzzled after solving a complicated problem, please look at this quote from "The Psychology of Computer Programming" book by Gerald Weinberg:
Similarly, it will always be difficult to appreciate a really good job of problem solving. Once the problem solution has been shown, it is easy to forget the puzzlement that existed before it was solved. For one thing, one of the most common reasons for problem difficulty is overlooking some factor. Once we have discovered or been told that this factor is significant, working out the solution is trivial. If we present the problem to someone else, we will usually present him with that factor, which immediately solves nine-tenths of the problem for him. He cannot imagine why we had such trouble, and soon we begin to wonder ourselves.
During the investigation, I found that sometimes I want to jump back to the previous hypothesis and try something I didn't notice. The format of the writing I'd chosen forced me to rewrite the ideas I had before. We can do better — we can have a dedicated paper for every single thing we plan to investigate. Here's how this could look:
After finally discovering where we need to apply the fix, let's go and do this!
My first story on Hackernoon is "The Meticulous Approach to Coding". It's helpful, and I will apply it here and reflect the flow in a separate article so this one doesn't become overcomplicated with excess details.
The first thing I need to do is to reproduce the problem. I've found the component where it occurs and now need to prepare the case displaying it. The way of setting a necessary state is rather challenging as well. And when I managed to do this in the end, the bug didn't reproduce.
There is a scheduling algorithm called round-robin. It executes processes in a cycle without priority. My brain and, possibly, yours has an association-based-robin which makes me jump forwards and backward when I see something inspiring me to switch the theme.
When I originated my first research by looking for all the "Saved Questions"
string, I looked through the PRs to find out where the issue could be. I noticed that one with the desired text arrived in PR #16808.
The burden of trying to set the proper state of the AccordionList
component and the fightback made me think that the bug might be no longer present. The jumping list might have gained the replacement when the "Add saved questions picker" PR made its way to the master
branch.
On Saturday, I asked if my suspicion is valid and received the confirmation on Sunday. The issue went to the closed ones and didn't require any fixes.
At this point, you and I might be unsatisfied that no PR made its way to the Metabase code. Thus we can feel unsuccessful here. If we try to see a broader perspective, our goal was to apply the method of bugs discovery, and we succeed in doing so. One can also add that the best code is no code at all.🙂
The essential thought to add here is that I used many debugging tools along the way: my IDE and its code discovery tools, React Developer Tools browser plugin, etc. I couldn't replace them with paper and pen effectively. The story's core idea is that you need a guiding light to help you unstuck in the very sticky process of debugging.
Thank you for reading! Please reach out on Twitter if you want to discuss the ideas I describe.