Be a tidy kiwi
The natural environment has huge importance in New Zealand. Coming in from an international flight you’ll see a recording from MPI warning you about the penalties for bringing in fruit or dirt to our country, and most travelers never end up interacting with the rest of our customs officers.
From a young age New Zealand children are bombarded with messaging to be a “tidy kiwi.” We’re told all throughout childhood about the importance of being good environmental stewards. Guardianship towards the environment—kaitiakitanga—is a cornerstone of our indigenous peoples’ culture.
A few weeks ago I walked through my local shopping mall, and was greeted by this billboard showing a kiwi bird collecting trash with its beak:
You don’t need to spend all day deep cleaning your local hiking trail to be a good environmental steward. The New Zealand model is very much a “clean as you go” one, where you are expected to do a little bit of cleaning here and there as you go through your day. You don’t have to pick up every soft drink can from the ground, but you should try to get at least one or two. Small efforts like multiplied across a population of five million yield large effects.
Your codebase is kind of like New Zealand’s environment. Removing small pieces of litter has a significant impact on its quality, and good citizenship dictates that you leave it in a better state than you found it. I’m a big proponent of sneaking small changes in alongside your feature work, to incrementally improve code quality over time. It’s one of the major cultural pieces I try to install in any team I work in.
Technical debt is the natural state of the world
This kind of “as you go” tidy up is actually pretty essential. We tend to be overly critical of technical debt; it’s easy for us to see a suboptimal solution and immediately declare its author an idiot. If we only had better engineers on the team and made better decisions we wouldn’t have to contend with technical debt.
In reality, the existence of technical debt is often a signifier of success. Assuming you have reasonable engineers on your team, those engineers will tend to make reasonable decisions given the state of the world at the time. The state of the world just happens to change rapidly if you are working at a successful company.
It’s legitimate to build a solution that only scales to 1,000 users if you only have 10 users today. Later on when we have 10,000 users we’ll curse whoever designed the original implementation but this is a good problem to have, and designing for limitless scale from the start simply doesn’t make sense.
The biggest risk to any business is that it will go bust because it either couldn’t find product-market fit, or couldn’t acquire customers fast enough. Engineers must make pragmatic decisions in this context, and this inherently results in the creation of technical debt. There is no way around this.
Cleaning up small bits of the codebase as you go is a great way to fight entropy and keep engineering velocity high over the long run. Too many engineering teams put off code quality work for too long, and end up backed in to a corner where new feature development is nigh impossible.
Cheap money used to give startups the ability to grow their engineering team forever, effectively “brute forcing” their way through the accumulation of technical debt. In today’s economy this is no longer a viable strategy, and startups need to be efficient.
Most of the time this work gets punted because people think that code quality improvements need to be major work items. This couldn’t be further from the truth! Being a tidy kiwi in your codebase comes in many different forms—big and small.
Ways to be a tidy kiwi
Small localized improvements can have an outsized effect on your team’s productivity, and they only cost a trivial amount of time. The RoI is immense; it doesn’t make sense to punt these actions forever.
Here are some small ideas on how you can improve the quality of your codebase today.
- Renaming a poorly named variable. Naming is hard, and oftentimes the person who wrote the code initially didn’t make the best choice.
- In my first week at Rye I renamed a variable named
shop
toshopId
. It wasn’t obvious whethershop
was an object or identifier, but now it is!
- In my first week at Rye I renamed a variable named
- Adding additional context to a log message. Almost every time I try to debug a production issue I end up running into a log message that would be useful if it logged an additional value.
- At CGA we needed to debug an issue with a bookings event handler and we were lacking basic information in logs like the booking ID. I added these values as metadata on this handler’s logs, to make future debugging easy.
- Adding an explainer comment. After spending 20 minutes figuring out the reasoning behind a tricky line of code, add a one sentence annotation to save the next person that time.
- I think we can all recall a time when we ran into a particularly inscrutable block of code. Think about how much more pleasant the experience would have been with one little comment!
- Resolving unnecessary problems. Volta.sh is a great version manager for JavaScript tooling, but it errors out without a lockfile. It’s easy to pin a
pnpm
version and push it up so the next person doesn’t need to solve the same problem.- At Rye our Shopify app didn’t have pinned tool versions, unlike all of our other codebases. I changed this so the next hire could get up and running faster.
- Removing a bit of complexity. Collapsing an unnecessarily complex class hierarchy so the code is more concrete is fast, and oftentimes it turns out that the added abstraction layers aren’t worth the cost.
- Deleting bad tests. If a test breaks every single time a codepath is touched, it’s probably too tightly coupled to implementation details and not actually testing anything valuable. Better to get rid of it and save the next person from investigating a failed CI run.
- We had a few tests at CGA which asserted on the exact text rendered by our frontend. We always ran into problems when the product team wanted to make copy changes. Rewriting these tests to assert more useful behavior helped a ton.
- Improving a type. Sometimes we can be sloppy with our type declarations, as we don’t have time to write out the perfect generic type. This often ends up leaking complexity into the rest of our codebase as we work around the inexact or otherwise loose type.
- Just this week I adjusted the type of a
getOffer
function at Rye to be a discriminated union; removing the need for all kinds of weird checks downstream. Now we can just narrow off atype
property.
- Just this week I adjusted the type of a