As programmers, it is satisfying to Write Nice Code. It can be very gratifying to think of yourself as a crafter, or artisan, someone fashioning flexible, elegant, extensible systems that can handle anything that is thrown at them. In this software elysium, everything Just Works™️, new functionality is trivial to add, data is emitted in a consistent fashion and is easy to analyse, and it is easy for engineers new to the system to understand what is going on.
Of course, that is a far cry from the day-to-day reality of professional software development, where needs to just-get-it-live or competing priorities on time rule the roost. In this reality, we often find ourselves doing the simple thing, accruing technical debt (consciously and otherwise), and needing to do more code spelunking than we’d necessarily like in order to shed light on opaque, hard-to-understand systems to our colleagues.
Of these two worlds, I’ll refer to the first of these as config programming, and the second as vibes programming. A sub-system characterised by config is one in which everything is parametrisable, can be easily understood and added to by your fellow engineers, with clearly-defined boundaries and responsibilities. A vibes-based sub-system, however, is marked out by an approach of just-make-it-work, has little clarity as to where there is scope for expansion, and is often painful to add new functionality to.
Setting up the two approaches in this way, it’s very easy to make quick, snap judgements that one is better than the other. After all, who wouldn’t want to spend their days writing and extending graceful, expansive software subsystems, especially when the alternative is wading through a quagmire of legacy code with hard-to-rationalise abstractions, names that don’t quite make sense, and comments spanning several lines explaining in excruciating detail why that method is doing what it’s doing?
The reality though is that we can’t have one without the other. At Cleo, we like to say, and codify in our Engineering Principles, that we do the simple thing, and that we think that technical debt is useful - in short, we love vibes programming.
We do so because it helps us ship software faster, to learn what is of value to our users and inform what we go on to build next - which features we double down on, and which we let go of.
However, this is not the only thing that we learn by writing and shipping software quickly. Through the vibes programming, we also learn more about the shape and constraints of the system as a whole. We learn where the system will flex, and where we bang up against hard walls. We learn about what features the system needs to support going forwards, about where bugs crop up, and how the engineers working on this software, our own internal users, feel about different aspects of it.
All of this is vital to understanding the ultimate design of the beautiful, pretty system that will abstract away all of the complexity, melt away your tech debt, and allow you to focus on delivering new features for users. At Cleo, we love code that that helps us to ship new software to our users faster, that lets us launch a new test in a day, that allows us to get new experiences into the hands of our users - in short, we love config programming.
Over the past couple of years at Cleo especially, I’ve been privileged to have helped facilitate the vibes-to-config transition of several of our subsystems.
I’ve witnessed an enum of squad names mapped against different values across several parts of the codebase grow into their own classes, living as first-class citizens to make it easier to pipe notifications about a/b tests into Slack, giving everyone in the team a better overview of what is live in the app and when.
And I’ve helped translate a 1200 long-line file containing a mish-mash of unrelated, disparate bits of logic into a series of well-bounded services, easy-to-rationalise services that non-engineers use to define audiences for different bits of functionality throughout the app.
The notion that technical debt is useful can often be a lightning rod for discussion about our engineering principles - some people love the honesty and realism, whilst for others it conjures up images of nightmare hellscapes of legacy code. The reality though is that a codebase is not one or the other. Technical debt is not only useful because it allows you to make the bad-code-for-fast-shipping trade-off. Technical debt is useful precisely because it allows you to understand the good code you need to write, in order to continue that fast shipping.