The Importance of Code Quality and the Impact of Technical Debt

Published: August 2, 2020
Anthony
By Anthony
11 min read
The Importance of Code Quality and the Impact of Technical Debt

It was my first day on the job and I had totaled eight hours of chugging coffee, banging my head at the keyboard for the fifth time, and fighting the desire to rewrite the entire codebase. According to my calculations, that's zero hours of productivity. At the beginning of my career I didn't care too much about code quality. As long as the code compiled and worked why did it matter right? Back in college, I was always pressed for time trying to finish programming assignments from four different Upper Div Computer Science courses. The programming assignments lasted at most a week or two and then we started a new project from scratch the next week. Hence, I never learned the importance of writing maintainable code. I kept this mentality with me to my first job and learned the hard way how difficult it becomes to add new features on top of bad quality code. The term for this is called technical debt or tech debt for short.

Wherefore Art Thou Tech Debt?

Let's describe the different ways tech debt can form. The most common form of tech debt comes from the backlog of bugs and hacks that have piled up in the codebase and nobody wants to fix. Other less obvious forms of tech debt include lack of tests, use of legacy frameworks and libraries, or a lack of thorough code reviews. I see this most common when teams have a tight deadline requiring teams to churn out code without maintaining good code. I also see this when the engineering teams are forced to start implementation without a clear set of requirements or UX mocks. Tech debt can also accrue from bad design due to lack of guidance from more senior engineer. In fact, you can design a great system at the time, but it may not age well over time due influences outside your control.

Unfortunately, there's a constant push and pull relationship between the business needs and engineering standards. This kind of debt is not the same as how businesses can take on debt to expand their business. For example, a pizza business can take on debt to expand their operations in hopes of bringing in more revenue in the future. Tech debt has an inverse effect where adding new features can decrease code quality. Let's imagine you have a button with square corners, but the UX team wants a new rounded corners button for just one page. While adding a new button with different styling doesn't sound like that much work to finish, you've just doubled the number of buttons you need to maintain.

Sounds Bad But If the Product Works, Why Should I Care?

Let's think back to that time you had to implement that hack you said you would fix later, but 3 months later you're hit with a bug that takes a week to fix. What about that time you wrote some incomplete code, but didn't leave some comments? Or what about that time you made a brand new util class instead of refactoring an existing class that did something similar? These are all small steps towards accruing tech debt.

Now let's look at a more severe case when all that unmanaged tech debt has come back to haunt you. The PM (Product Manager) comes asking you to implement that new shiny feature. You start coding away, but realize how difficult it is to implement this feature without adding more hacks on top of existing hacks. However, you succeed at implementation the new feature within a week. A week later you come back to the same codebase and you have no idea what you wrote. Ok whatever right? A new sprint cycle starts and your PM comes back with another feature request and you start churning code, but this time it takes you two weeks to implement this feature due to how complex the codebase has become.

TLDR: Unmanaged tech debt can lead up to a point where it becomes impossible to add new feature, extend deadlines beyond control, or demotivate engineers to touch that code. It can also make it easier to introduce unexpected bugs since the codebase becomes more brittle and fragile.

How can we tell when too much tech debt has incurred?

Tech debt is impossible to avoid, but there are definitely ways to mitigate it as long as you recognize detect it. Let's look at some obvious signals of incurring tech debt.

  • Too many parts of the code that have been copy pasted, but can be shared
  • Unclear code or lack of comments
  • The existing codebase has grown in an unexpected manner making it harder to make new features
  • Code becomes more tedious to test via unit/integration tests
  • Too many TODOs and HACKs in the code
  • Lower velocity

The last two points can be tracked with tools such as Jira or some other project management tools.

Too many TODOs and HACKs in the code

A good practice to track TODOs, bugs, and hacks is to create a tracker for each item and add a comment in your code so others can easily check the bug. I like to do the following:

// TODO(issues/1#issue-670532172): Remove the name param once we support id to name conversion.
function doThing(id, name) {} 

// HACK(issues/1#issue-670532172): Setting document.window so we can work around blah blah.
document.window = windowWorkAround();

This makes it much more apparent which parts of the code are using a hack or needs to be updated in the future. Anyone reading the code can easily lookup the tracker and read the tracker for more context.

There were many times I told myself I would go back and fix that bug, but forgot about it a week later. So having a solid record makes it easier for you and other team members to see what's going on. Leaving a record is also important when people leave the team. Not everyone stays in the same team or company their entire career, so it's best to leave some context for that poor soul who will have to handle that bug when the original author has left. But in practice, people don't usually go back and fix bugs even if there is a paper trail. A good way to approach the backlog of bugs is to incite your team to have bug bash sessions which we'll talk about later in this article.

Lower Velocity

Let's first talk about what is velocity. Velocity is a measure of how fast a team can deliver a requirement. Velocity can be calculated by how quickly the team can complete tasks during a sprint which are usually weekly or biweekly. These tasks are also paired with an estimated time or complexity to weight the task appropriately. The velocity of the team can be used to determine how much work a team can take on for that specific sprint.

Tech debt can impact velocity and usually in a negative manner. Usually it can make the estimates higher than it should be since features might be harder to fix or refactoring is required. Sometimes tasks are underestimated and end up taking much longer to complete due to the required refactoring or the extra hack. Other times it can cause the team to want to avoid taking on ownership of a specific task due to how difficult it is to address. All in all, unaddressed tech debt can and will extend development time.

How to manage tech debt

Throughout my career, I've seen teams approach tech debt in many different ways. I've compiled a list of some of the methods to do so, but I think the first step to take is having a culture change.

Changing Team Culture Regarding Code Quality

As a junior Software Engineer, I wasn't aware of the implications of tech debt until I attended a Code Health conference. I'm now a firm believer that team awareness on this topic is critical in order to change the team's mentality towards tech debt. Most developers would rather create brand new features than fix some bug. Who doesn't right? New features are usually the most existing things to add and create impact. This is why it's important to have a good team culture regarding fixing bugs. The team should weigh those bug fixes just as important as anything else because it impacts the overall team indirectly.

If Something is Broken, Fix It

If your house has a broken window, would you leave it broken or would you fix it? You can take the easy route and ignore the window, but this can lead to more troubles later. What if it rains or snows? You can also just board up the window with some wood, or you can spend some quality time and properly fix it. You can view tech debt in the same manner. Sometimes the best way to manage tech debt is to go out of your way to fix it. Take the initiative even if its not within scope or if it will be rewarded because your team your appreciate your efforts. You can read more on the broken window theory here.

Having a Rotational On-duty Person

In my team, we have a weekly on-duty rotation where the on-duty person stops working on their features for the week to focus on the backlog or fixing broken integration tests. This will ensure tech debt is never neglected since someone will always been looking into it. This is also a great way for new members to onboard and learn about the whole system since they can work on parts of the code no one usually touches.

Pushing Back On Feature Requests

Sometimes the best way to balance tech debt is to work with PMs and UX on compromise. If they ask for a certain feature that requires a hack try to work with them on alternate solutions so all parties can be happy. It is also important to communicate how long features may take due to required refactoring. I don't think we should say no completely, but having a compromise on both ends maintains a healthy balance between new features and tech debt.

Recurring Bug Bashes

Push management to dedicate time to handle tech debt because often times, companies don't. Maybe dedicate one sprint cycle during a holiday season when there isn't much work to do. My team does is a yearly bug bash where we stop working on features and focus on any lingering bugs, hacks, or infrastructure improvements for a week. To make it fun, we track the number of bugs fixed and reward the top contributor a small prize. Don't ignore all your tech debt until these bug bashes occur though!

Migrating Pieces of Legacy Code

I've noticed tech debt incurs in both new and legacy codebases. However, legacy codebases are plagued by old frameworks, libraries, programming languages, and old coding conventions. Sometimes the best way to tackle this is by slowly migrating those pieces of code. If you're dealing with a monolithic server, consider breaking it up into micro services piece by piece. If you're a frontend developer using an old framework, look into building new components in a new framework. You don't always have to be locked down to your current tech stack just because the it's been that way for ages.

Convincing Management with Metrics

My team spent the last 4 years rewriting the entire product from scratch to abandon the decade old tech stack it was written on. Four years is a long time to spend rewriting an entire tech stack and halting any feature development, so how did we get approval? It's important to quantify and communicate to leadership the benefits and expected outcomes from handling tech debt. Some good metrics to communicate can include faster release times, a more comprehensible codebase, faster development times, how much money can be saved in the long run, and feasibility for new features. Having charts or tools to visual the outcomes is also useful for non-technical people. For example, our team switched our tech stack that supported server-side rendering and better caching which improved initial load time. We convinced leadership that this feature was worth pursuing since studies have shown decreasing page load time can increase conversions. You don't need to completely start over like my team, but leadership needs to convinced there is some sort of positive impact towards the business.

Conclusion

Has tech debt plagued you in the past? What did your team do to approach it? Did you enjoy this article? If so, check out this article on Programming in School vs Working as a Software Engineer and don't forget to leave a comment in the comments section below!

Related Posts

Copyright 2020 © Mai Nguyen. All rights reserved.