Pangram verdict · v3.3
We believe that this document is fully human-written
AI likelihood · overall
HumanArticle text · 1,919 words · 5 segments analyzed
Table of ContentsCharacteristics of the Best Simple System for Nowfor NowSimpleBestThe case against BSSNIt is overkill for a prototypeIt is incompleteIt is inefficientWhy do we not do this?Good habitsCourageHumilityWhere do you start?Example: the JSON libraryExample: the XML streamertl; dr 3 Feb 2025 26 mins You can have your cake and eat it, as long as you bake it carefully.‘We can do this the quick way and pay later, or the thorough way and pay now.’ This seems to be a fundamental dichotomy in software development, between ‘perfectionism’ and ‘pragmatism’, but I do not think it has to be a trade-off at all.A CTO friend uses the metaphor of clearing two paths up a mountain. The left-hand path is quick and dirty, cleared with a machete and brute force—you do not expect anyone to follow you, you just hack your way through—but you make progress quickly. The right-hand path is a wider, clearer, paved path, and more substantial, but takes time to build as you go along. The left path is useful for scouting ahead to see what is coming up along the trail. The right path is where you will lead everyone, knowing that the trail is safe.My friend lives in a constant tension between folks who take the left path—which also happens to be the fastest way to make money (he works in a trading firm so this argument carries a lot of weight!)—and the right-path folks who want to build a resilient, sustainable product.This describes many organizations I have worked with. The tensions on either side are based on the idea that the ‘other way’ is doing it wrong. The right-path advocates are worried that the left-pathers are racking up technical debt that is going to come back and bite them, under the guise of ‘pragmatism’. The left-path folks think the right-pathers are ‘perfectionists’, over-engineering and gold-plating things (and there are often plenty of business managers lining up to agree with them).I propose that there is a middle path both sides are missing. I call this path the Best Simple System for Now, or BSSN. It has taken me a long time to formulate this idea, which has been ‘hiding in plain sight’ for many years.
There are several source ideas that either imply this or at least hint at it, which I hope to acknowledge here, but I have never seen it stated explicitly. So here it is.Characteristics of the Best Simple System for Now #The Best Simple System for Now is the simplest system that meets the needs of the product right now, written to an appropriate standard. It has no extraneous or over-engineered code, and any code it does have is exactly as robust and reliable as it needs to be, neither more nor less.Each part of the phrase Best Simple System for Now is deliberate and each part is mutually reinforcing. Any deviation in any of these means it is no longer the Best Simple System for Now, but something strictly weaker. Let’s unpack each of these parts.for Now #The system should not anticipate the future in any way. This is counter to pretty much any advice I have ever received—or given—as a programmer!When we programmers get our hands on a problem, we bias towards a general solution. There is even an xkcd about it, so it must be true:We are never more than a small leap away from ‘It’s a rules engine!’ or ‘It’s a state machine!’, so our efficient engineering mind decides to save some time—and rework—and reach for this solution right away. Part of this decision is the knowledge that each change carries cost, so taking several small steps to get somewhere is always going to be less efficient than taking the one big step we ‘know’ that we need.We rely on automatic and unconscious pattern-matching to make room for conscious processing. In Thinking Fast and Slow, Daniel Kahneman refers to these as Systems 1 and 2 respectively. Pattern matching is an unconscious System 1 activity; we are good at it and it comes naturally. ‘Seeing what is really there’ is harder and requires conscious thought and effort.Designing for now is the antithesis of this. It is the art of seeing what is really there in spite of the patterns that your brain is presenting to you. This is the crux of the BSSN—which might be pronounced ‘bison’, as a companion to all those yaks we usually end up shaving.Simple #When I am designing something, there are two versions of me, usually in conflict.
The ‘clever’ version of me—the one with the decades of experience and the big ego–knows that this thing over here will change next, so we should make that thing an interface. Or that we will almost certainly have this volume of data so we should go ahead and build this concurrent version of the processing algorithm instead of the straightforward serial one. The more humble me knows that I will be close-but-wrong with any of my predictions, in the way I have been close-but-wrong so many times before, so maybe hold off for now. The more humble version usually loses.So when the future happens, I end up in one of two modes: either working around the assumptions I made because my prediction was not quite what happened next, or backtracking and changing the code back to something that I can flex it in the way I need. But do not worry, my clever self assures me, this was a one-off, and next time I will get it exactly right! This happens to me time and again. I am well over 30 years into writing software for money so you would think I would have figured this out by now, but no.Having seen and worked in many codebases over those years, I am quietly confident it happens to everyone else as well. Most code I encounter has speculative seams and interfaces that are decades old and which were never exploited; choices of algorithm and technologies that would have been fantastic for 20,000 concurrent users rather than the twelve people that it serves; hooks and extension points for ‘scalability’ and ‘flexibility’ beyond my wildest dreams, perhaps written in the hope that this would be their magnum opus system, the one that defined their career. And of course this code is littered with subtle bugs due to the interactions between all this complexity.What I have learned, time and again, is that simple is a function of now. In other words, I am continually looking for the least complexity for the current requirements and constraints and nothing else. When these requirements and constraints change, the definition of ‘simple’ necessarily changes with them.Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.— Antoine de Saint-ExupéryBy this definition, we seek perfection! If you can take anything away from the current system and it still does what it needs to for now, then it never belonged there.
A BSSN does not achieve ‘future-proofing’, ‘scalability’, or ‘flexibility’ by catering for all possible futures, but by being so simple that it can flex however we want it to. If it needs to handle higher load, we are confident we can achieve that. If it needs to manage a wider range of inputs, or provide different validation, or integrate with unexpected upstream or downstream services, we are confident we can do that too. If it needs to evolve along a dimension we have not even considered yet, we will be able to do that at least as easily as with any other solution.There are no speculative interfaces, no overly broad data types, no generic functionality where specific code will do. Instead it is highly opinionated and deliberately narrow in its design.Conversely, I have experienced Gall’s law1 in the wild countless times, from the enterprise data dictionary to the generic workflow solution to the click-ops configurable rules engine, there is an inexorable 3-5 year arc towards failure.A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over with a working simple system.— John GallBest #Keeping things simple does not mean cutting corners. In any situation there is a right way to do things, and we can choose to do them this way. The challenge for both the perfectionist and the pragmatist is that the ‘right way’ is contextual; code that will underpin core business functionality should have a higher quality bar than a sketch of an experimental feature. Underinvesting in one is as much a risk as overinvesting in the other. In the former case, the risk is stability and resilience. In the latter, opportunity cost, which I discuss below.Adding to this, the ability to ‘sketch’ code is as much a skill as engineering a robust, resilient solution. Sketching does not mean hacking! I know many more engineers who are good at ‘proper’ code than those who can sketch well. Sketching is about providing just enough quality that you can sustain a direction, knowing you can go back and fill in the blanks later on (or throw away the sketch with minimal investment lost). Hacked code quickly becomes unmanageable.
Well-sketched code is just a lighter-weight version of the real thing.When Ward Cunningham talked about ’the simplest thing that could possibly work’, this is what he meant. He was not invoking some Platonic ideal of simplicity so much as whatever tiny step he could try that would move his understanding forward. In a lovely interview with Bill Venners from 2004, he observes that ’the mere act of writing it organized our thoughts’.The best code is intention-revealing; it strikes a balance between cohesion and coupling, making it easy to navigate, easy to reason about, easy to change.If there is duplication, or worse, near-duplication, of ideas in the code then this is not the best code. Conversely, if the code is obsessively DRY, causing coupling just because the code in two places looked similar, then this is not the best code. If the code does not have domain terms front and centre, mapping directly to your real-world use case, then this is not the best code.I have written about what I consider good code, joyful code, which I formulated as CUPID:Composable: plays nice with other code; has minimal dependenciesUnix philosophy: does one and only one thing; obviously and comprehensivelyPredictable: in its behaviour, observability, runtime characteristics, failure modes; also in what happens when you change itIdiomatic: uses patterns and idioms familiar to a developer experienced in this technologyDomain-based: intention-revealing in naming, behaviour, and code structureIt is important to note that you can write joyful code just as quickly as—in fact faster than—hacky, ‘pragmatic’ code. CUPID builds on Richard Gabriel’s idea of habitability of code, which he describes as feeling comfortable and confident working with a codebase.You do not need days or weeks of deep, considered thought to write the best code, but you do need great habits. You develop these habits over time, ideally by working with others who hold these values and who care enough to share their habits with you. I have been lucky to work alongside folks who have been generous with their time and knowledge. Sometimes I talk about them.You will often hear XP techniques such as pair-programming or test-driven development mentioned as a prerequisite for good code, but do not be put off by the zealots or ideologues.