Choosing between building up technical debt and missing delivery deadlines is a false dichotomy, Daniel Terhorst-North argued in his talk Best Simple System for Now at GOTO Copenhagen. Programmers love to generalize rather than solve the immediate problem at hand, which can make future changes difficult. Instead, we need to build the skills and instincts for keeping things simple.
Many programmers think they continually have to make a trade-off, racking up technical debt to meet a deadline or pushing back on deadlines imposed by management or commercial folks so they can have the time to "do it right":
With the right trade-offs and design decisions, you can have a shippable product all the time, while keeping the quality bar high enough.
The Best Simple System for Now (BSSN) fulfils three characteristics, Terhorst-North explained:
- It has no extraneous or speculative code, just what is needed for now ("Simple"). The design is "future-proof" in the sense that changes are easy because it is so simple, not because we put extension points wherever we thought things might change.
- It meets all the current requirements ("for Now"), while carefully ignoring or deferring any future needs.
- Any code that is there is written to a suitable standard ("Best"). For production code, this means telemetry, automated tests, API documentation, automated path to live, etc. For "sketches" where you are exploring ideas, the bar can be lower. But in either case, there is no excuse for poor naming, unnecessary complexity—large source files, unwieldy methods or functions, poor file structure, etc.—that "good habits" cannot take care of.
Terhorst-North referred to a description of the skills a witch needs by Terry Pratchett in one of his Discworld fantasy novels Wintersmith:
First Sight and Second Thoughts, that’s what a witch had to rely on: First Sight to see what’s really there, and Second Thoughts to watch the First Thoughts to check that they were thinking right.
Designing for now involves first sight: seeing what is really there, Terhorst-North said:
Do we need a rules engine, or is this just half-a-dozen if-statements in a trenchcoat? Is Azure-hosted kubernetes the right deployment platform for your internal web app with its 10 users?
Programmers love to generalize, which introduces layers of complexity that we have learned to ignore. We have lost our first sight, Terhorst-North argued. This can make future changes difficult, especially if they are in a direction we did not anticipate.
The advice from Terhorst-North for keeping things simple is to just try it, knowing you will get it wrong time and again. You will overthink, over-code, and overindex in one direction or another while you build the skills and instincts of keeping things absurdly simple.
Mostly bad habits and learned behaviours inhibit people from designing simple systems that focus on the essence, Terhorst-North explained:
As a programmer with decades of experience, I have a huge ego, and I am convinced I know a lot about programming, so I find it difficult to admit that I will be close-but-wrong whenever I try to predict the future direction of a product or codebase.
Even though he tries to adhere to BSSN habits whenever he writes code, he still finds himself overthinking or over-engineering a solution "just in case", Terhorst-North said:
The only way through this that I have found is to practice, practice, practice! In my case, I find I am a lot more honest when I am pairing; my pair stops me from gold-plating, or challenges my assumptions about where we are going next, in a way that keeps me on track.
Before he knows it, he is neck-deep in yet another optimization they will never need, or adding another interface or flex point that will never be exercised, Terhorst-North concluded.
InfoQ interviewed Daniel Terhorst-North about the best simple system for now.
InfoQ: You advised against anticipating the future in any way. Why?
Daniel Terhorst-North: This is the crux of designing for now. A product could change in all kinds of ways at any time and for any reason. I call these "dimensions of change".
Here are some examples:
- Many additional types of report but we chose a domain-specific reporting tool with huge domain flexibility that can’t connect to all these additional data sources
- Many variants of the same report, but adding a new variant is expensive and time-consuming because we locked that down "for performance reasons"
- Increased report complexity because compliance needs data provenance, but this will massively slow down report generation because we optimized for scale rather than detail
- We want a dashboard instead, so the "report" is going to be evaluated dynamically
These are not hypothetical; all of these have happened to me! Whichever of these possible futures we predict or preempt in our design, we are making trade-offs against the others and introducing complexity entirely speculatively.
My position is that the key to real "flexibility" and "scalability" is to keep things as simple as possible so you can pivot towards the next change, whatever it is.
InfoQ: What can be done to keep our systems simple and best?
Terhorst-North: I advocate for "fake it ’til you make it", or rather, "act your way to a new way of thinking". You cannot convince someone that it is possible to continually keep a system simple, nor can you convince them that any future change will be easier the simpler the codebase is; they have to experience it for themselves, several times. Ironically, the more experienced the engineer, the harder they find it to believe that they should not be anticipating the next dimension of change. After all, isn’t that the point of all that experience?
I no longer obsess about whether I have the "right" design. I am confident enough in my habits—TDD, version control, refactoring, "sketching" code—that I know I can back out any poor decisions quickly enough and set off in a different direction. I have also taught myself to not believe my inner monologue when it tells me it "knows" what is coming next.