Learning When to Cut the Thread

As software engineers, we frequently tackle complex problems in code. The source of a problem's complexity may be from the problem itself, from the code's architecture, or from something else entirely. In this post, we're going to compare writing code to pulling on a loose thread from a piece of clothing. There comes a time when writing software that we need to know when to "cut the thread" or we'll risk the whole thing unraveling.

Aaron Bos | Tuesday, December 14, 2021

The Analogy

If you've landed on this post you may be a little curious what pulling on a loose thread from a piece of clothing and writing software have to do with each other. At a high level, the activities have nothing to do with each other, but if we take the time to look a little closer I think there are some similarities to learn from.

First, let's talk about pulling on a loose thread. The thread may be on a pair of socks, a blanket, a t-shirt, or any piece of material that was sewn together. If you've never pulled a loose thread before, then the thought of doing so may not seem like a big deal. I can assure you that pulling a loose thread can be a big deal. The reason is that in some stitching methods pulling a single thread can cause the entire piece of clothing to unravel. There is a possibility that the loose thread will just come right out and cause no harm, but that can't be known unless we keep pulling.

Now you may be wondering, "What does this have to do with writing code?", so I'll do my best to explain. If we think of a codebase as an article of clothing and a bug or feature as the loose strand, we can start to see some similarities between the uncertainty of pulling a loose thread and the uncertainty of fixing a tricky bug or adding a new feature. Let's take fixing a tricky bug as an example.

When tasked with tracking down and fixing a bug in our code, the first step will be reproducing the bug. This is similar to spotting a loose thread in a pair of wool socks. There is some imperfection that is causing issues for our users. Now we need to decide how to fix the bug. In our code that may mean fixing a syntax error, adjusting some boolean condition, refactoring the code, or rewriting an entire layer of the application. With the wool sock we may want to leave the loose thread, pull it a little bit and trim it, just trim it, or try and pull the entire thread out. At this point, we are just trying to determine the best way to solve our problem. , Once we've decided on a course of action we dive in. After a bit of time we realize that our proposed solution may not have been as easy as initially thought. The bug permeates through several layers of our codebase and the changes begin to ripple throughout. We had chosen to pull the loose thread and we soon realized that the end of the thread may not be in sight. At this point, we have a decision to make as a software engineer and as someone who doesn't want a loose thread in our sock. We can either keep pulling the thread (expand the scope of change and potential blast radius of fixing the bug) or we can cut the thread (determine the scope of the problem and potentially plan out finely scoped changes for the future). From this point on we're going to look at these options purely from a software engineer's lense to avoid belaboring the analogy.

Reasons to Keep Pulling

The idea of continuing to pull on the thread relates to the feeling of uncertainty and potential fear when the scope of a code change begins to expand rapidly. Personally, there have been times when I've begun making code changes with the intention of fixing a small bug, but a few hours later I find myself having to dig deeper than expected and changing a large number of files. As software engineers, we need to be mindful of this situation and make a conscious decision on how to proceed. In this section, we'll discuss a couple of reasons to continue expanding the scope of the code change to solve the issue at hand. The first reason we're going to discuss is the need to refactor.

Refactoring is the process of changing code (hopefully for the better) usually with the intention of keeping the code's behavior consistent with the previous implementation. For example, we may refactor some code to make it more readable by extracting blocks of code into separate methods or components. To come back to the thread analogy, refactoring is similar to pulling on a thread where we are confident there is an end in sight. When taking on refactoring, it's important to have a plan in place so that the code is left in a better place than when we started working on it. If we can't be confident in the benefits of a refactor, it may not be worth pulling on the refactor thread at that time.

A second reason for pulling on a particular thread is when the issue has been recurring or frequent in nature. I think this reason is most applicable to bugs or sticky parts in a codebase. For example, if a bug is reported and this is the first time we've seen the bug it may be best to find the quickest and easiest way to fix that bug. There is no point in spending a large amount of development time on a bug that occurs infrequently. On the other hand, if the reported bug impacts a large number of users or has been reported often, it may be time to pull on the thread and see what comes loose. Often times the longer a bug fix is put off, the harder it is to address. As engineers, we will encounter these decisions on a daily basis and over time the choice of a quick fix vs. expansive fix will become easier to make. Oftentimes these decisions come with trade-offs and as difficult as it may sound, we need to be comfortable with uncertainty. onto The important thing to note with continuing to pull on the thread is that it is very easy to pull too much to the point of unraveling. If we have chosen the path of pulling the thread, we need to keep the end of the thread in sight so that we can avoid unraveling the whole thing.

Reasons to Cut the Thread

We've just discussed some reasons to keep pulling on the thread by embracing the uncertainty that can come with poking and prodding code to make it function as expected. Now we're going to go through reasons to avoid the expansive changes and cut the thread before the whole solution unravels. In a lot of ways cutting the thread may seem like the easy way out. We may opt for the quick fix, which introduces some tech debt. We may quickly bolt a new feature onto the system to avoid refactoring properly. These may sound like poor options, but sometimes they're the right option for the problem at hand.

For every reason provided to keep pulling on the thread, we have the opposite option of cutting the thread. It's possible that the current project is on a tight deadline with no wiggle room for completion or there is a bug that is affecting a large number of users. It is important for us and our teams to fit our decisions into the problem space. Although refactoring may sound exciting, it may be best to bolt on the quick feature and move on. These decisions are never easy and they're also not usually made alone. In many cases, software development is a team sport that requires effective teamwork and communication. We need to learn to communicate options to both technical and non-technical stakeholders to allow the most effective decisions to be made.

Having been involved in software for several years, I've come to understand that these problems are presented to us daily. I am still learning to make the right decisions, but being cognitive of the dichotomy of pulling on a thread vs. cutting it can go a long way in helping teams effectively solve the right problem at the right time.

As always thank you for taking the time to read this blog post!