Category Archives: Tech Debt

Four Ways to Augment Code Coverage

Code Coverage by itself is a hard metric to use because it can be gamed, and so it will suffer more from Goodhart’s Law, which is summarized as “When a measure becomes a target, it ceases to be a good measure.” Goodhart’s Law observes that if you put pressure on people to hit a target, they will, but maybe not in the way you wanted.

And this would happen with code coverage because we can always increase coverage with either useless tests, tests of trivial functions, or tests of less valuable code.

I use these metrics in combination with coverage to make it harder to game:

  • Code Complexity: The simplest way to do this is to count the branches in a function. I use extensions in my code editor to help bring complex code to my attention. If coverage of the function is also low, I know that I can make the code less risky to change if I test it (or refactor it).
  • Usage analytics: If you tag your user analytics with the folder that the code generating it is in, you can later build reports that you can tie back to your coverage reports. See Use Heatmaps for iOS Beta Test Coverage. In that post, I used it to direct manual testing, but it would work for code coverage as well.
  • Recency of the code: To make sure that my PRs have high coverage, I use diff_cover. This makes it more likely that my tests are finding bugs in code that is going to be QA’d soon and has already been deemed valuable to write. Very old code is more likely to be working fine, so adding tests to it might not be worth it. If you find a bug in old code worth fixing, it will generate a PR (and become recent code).
  • Mutations: I am still trying to find a good tool for this, but this lets you test the quality of your assertions in addition to your coverage. I do it manually now.

Generally, the way to make a metric harder to game is to combine it with a metric that would be worse if it was gamed in ways you can predict (or have seen).

Mutation Testing

A couple of years ago, I wrote about a testing technique that I had learned, but didn’t remember the name of, so I called it code perturbance. I mentioned this technique in my book, and a helpful beta reader let me know the real name: mutation testing.

The idea is to intentionally change the code in a way that introduces a bug, but is still syntactically correct (so you can run it). Then, you run your test suite to see if it can find the problem. This augments code coverage, which will only let you know that code was run in a test, not if an assertion was made against it.

Now that I know the name, I can find out more about it in google. For example, there are tools that can do it for you automatically. The one that I’m most interested in is Stryker-mutator, because it supports TypeScript. I’ll report back when I try it.

Technical Debt Typology Research Paper

A few months ago, I got an email from Mark Greville that included a link to a research paper he coauthored, called A Triple Bottom-line Typology of Technical Debt: Supporting Decision-Making in Cross-Functional Teams.

In the paper, the authors identify several categories of tech debt. One category is internal vs. external effects. In my book, I also identified the external category, which I call visibility. The paper thinks of the entire business as the “internal”, but I think of the team itself as the internal part. My separation is driven by difference in communication that the engineering team needs to use for itself vs. the rest of the business. Customers and other public stakeholders would likely be similar to the non-engineering business teams.

Since a lot of my book is about how tech debt affects developer productivity, I break down internal to the various ways it could reduce productivity. I use misalignment to describe tech debt that doesn’t meet the documented standards of the team. When the code is hard to change, for example if there’s messy code all over the codebase (Marbleized Code Fat), I call that resistance. If the code does what it’s supposed to (so no external effects) and the customers highly depend on its behavior, I warn about the risk of regressions.

Another pair they describe is whether the tech debt is taken knowingly or unknowingly. This is useful from a taxonomy perspective and might contribute to tech debt avoidance, but in my book, I write:

I don’t think of tech debt as the result of an intentional shortcut borrowed from the future. Some debt starts that way, but the reality is that lots of tech debt happens because the world changes. Even if your system represents your best ideas of how to solve the problem at hand, your ideas will get better, and the problems will change. You can do everything right and still have bad code, so it doesn’t help to judge the decisions that got us there. Learn from them, but it’s counter-productive to dwell on them.

My chapters on these dimensions focus on using them to decide what to do about the debt, and I don’t think intention is a factor in deciding what to do next.

The paper is worth a look and also has quite a good bibliography if you are interested in research on tech debt. Since the methodology of the research included a literature review, the list of references reviewed is another treasure trove of research.

Adam Tornhill on Tech Debt’s Multiple Dimensions

In the research for my book on technical debt, I ran into this talk by Adam Tornhill:

Adam has a similar perspective to mine: technical debt is multi-faceted, and the right strategy should address its various dimensions.

One of his examples is combining a code complexity metric with data from your source code repository to define low code health hotspots—areas where code is both complex and frequently changed. To find the hotspots, he built a tool to calculate this metric and visualize it. In the video he shows data from big open-source projects (like Android and .NET core) and pinpoints areas that would benefit from work to pay down debt.

Similarly, in my book, I identify eight dimensions of debt. Complexity is something I consider to be part of Resistance, which is how hard or risky it is to change the code. I would also incorporate low test coverage into resistance, as well as subjective criteria. Adam says that complexity is a good estimate of how many tests you need, which is true, and I give you credit for having the tests. I am mostly concerned by complex code that is undertested.

Like Adam, I believe that bad code only matters if you plan to change it. He believes that the repository history of changes is a good indication of future change, which I agree with, but to a lesser degree. In my book, I recommend that you look at the history and shared this git log one-liner as a starting point:

git log --pretty=format: --name-only --since=3.months | sort | uniq -c | sort -rg | head -10

That line will show you the most edited files. To find the most edited folders, I use this

git log --pretty=format: --name-only --since=3.months | sed -e 's/^/\//' -e 's/[^\/]*$//'  | sort|uniq -c|sort -rg|head -10

This data contributes to a dimension I call volatility. It’s meant to be forward looking, so I would mostly base it on your near future roadmap. However, it is probably true that it is correlated with the recent past. In my case, this data is misleading because I just did a reorganization of my code to pull out a shared library to share between the web and mobile versions of my app. But, knowing this, I could modify the time period or perhaps check various time periods to see if there’s some stable pattern.

Generally, my opinions about tech debt and prioritization are very aligned with what’s in this video, especially the multi-faceted approach.

Marbleized Code Fat?

I go back and forth on whether the name “Tech Debt” is the most useful term. In my book, I decided I can’t fight the term, so I use it, but in my opening paragraphs I make an argument that the problem we call tech debt isn’t like other debt.

The most common way I’ve seen tech debt is that it’s just everywhere. It’s not limited to a specific old module, it pervades the codebase like marbleized fat in a good steak, but not as delicious.

Follow-up on my Tech Debt Article in The Pragmatic Engineer Newsletter

About two weeks ago, The Pragmatic Engineer published an early excerpt from a book I’m writing on Tech Debt. This week, he published a follow-up that’s available for free. Read it here:

https://newsletter.pragmaticengineer.com/p/paying-down-tech-debt-further-learnings

If you missed the original article, it’s here: 

https://newsletter.pragmaticengineer.com/p/paying-down-tech-debt

After that article came out, there were a few LinkedIn posts with good conversations about tech debt. This one by Ellery Jose has an interesting table that breaks down how to treat technical debt as a financing decision:

​https://www.linkedin.com/posts/ejose_tpm-pmo-engineering-activity-7237750795348164610-5mKq/​

Also, Guillaume Renoult sent me this post he wrote:

​https://medium.com/elmo-software/how-to-sort-out-tech-debt-and-speed-up-development-9a1d27fdd39e​

There is a section of my book that has similar ideas to these two posts, particularly team management of debt using evidence and data. I’m hoping to have a draft done of that part in a few weeks. If you want to see an early version, sign up to receive updates.

My Guest Article on Tech Debt for the Pragmatic Engineer

I started writing a book on Tech Debt in January and I posted a few chapters in the Spring. A few months later, Gergely Orosz from the Pragmatic Engineer reached out to ask me to write a guest post for The Pragmatic Engineer newsletter—it just went up today.

https://newsletter.pragmaticengineer.com/p/paying-down-tech-debt

If you want updates about the book, sign up here: Pay down tech debt to go faster right now

There are a lot of posts on this site about tech debt. Here are a few

Policies that reduce dependencies

Over time I have become skeptical of most dependencies. I wrote in Third-party Dependencies are Inherently Technical Debt:

[…] any third-party dependency is technical debt. Third-party? Here’s how I am defining it for the purposes of this article. You are the first party, the platform you are writing on (e.g. iOS, Android, React, NodeJS) is the second party. Everyone else is third party.

[Third-party dependencies are debt because] you need to constantly update them, […] they introduce breaking changes, […] they become unsupported, […] your platform adds their own incompatible implementation, [… and] they don’t update on the same schedule as the [platform].

To that end, I like policies that tend to reduce the number of dependencies you have. Here are a couple that I have seen work.

  1. Become a committer on any third-party dependency you take on. To be fair, you kind of owe that to the project.
  2. Donate to any third-party dependency you take on where you won’t become a committer.
  3. Fork the dependency and bring in updates carefully.

Seems like extra work, right? The extra work is why they work.

PM-led vs. Engineering-led Time

At Trello, on the mobile team, we had a formal way of allocating engineering time to either working on PM-led or Engineering-led initiatives.

A PM-led initiative was something that ultimately rolled up to a OKR that was well-understood by the business. The requirements came from the PM and the PM could assess acceptability. An Engineering-led initiative was something like tech debt, changing our dependency package manager, improving CI build times or anything where the requirements came from the team itself and the PM didn’t know or care about it (and neither did anyone outside the team).

So, let’s say we decided the split was 60% on product manager led initiatives and 40% on what we called engineering-led. The split is arbitrary—the EM and PM agreed on what was appropriate for their team and set it for the year.

Then, any individual engineer at any one time (for a couple of sprints) was on either a PM-led project or an Engineering-led project. We did not want a single engineer to be split across PM/Eng-led work. This made it easy to know we were allocating correctly (without having to track time on individual stories or cases).

So, if it was 60/40 and we had 10 engineers, 6 would be on PM-led, and 4 would be on engineering-led at any one time, but it rotated.

This just needed to be mostly right over the course of a year—on any specific month it could be a little off if over the long-term, it matched.  For example, If the PM didn’t have work ready, we could do more engineering-led work temporarily.

January 2023 Roundup

I have two things taking most of my time these days, and I’ve been using them to give me topics to write about.

I am working on a book on Tech Debt

I am working on personal productivity software

I also announced season 4 of my podcast, which I am still working on. I hope that there will be episodes in February, but not sure yet.