Best Simple System for Now
The Optimal "Simple System for the Moment"
By Dan North & Associates Limited
Date: 3 Feb 2025 | Reading Time: ~26 mins | Tags: #programming #software #agile
"You can have your cake and eat it, provided you are careful about how you bake it."
In software engineering, we often face a perceived binary choice: do we move fast and pay the price later, or do we do it right the first time and pay now? This is typically framed as a clash between pragmatism and perfectionism. However, I argue that this trade-off is an illusion.
The Mountain Path Metaphor
A CTO colleague of mine describes this struggle using the imagery of carving two different trails up a mountain:
- The Left Path: This is the "quick and dirty" route. You hack through the brush with a machete. It's not meant for a crowd; it's for the scout to see what lies ahead as quickly as possible.
- The Right Path: This is a wide, paved, and sturdy road. It takes significantly longer to construct, but it is the path you eventually lead the rest of the organization down because it is secure and reliable.
In a high-pressure environment (like a trading firm), the tension between these two approaches is constant.
Comparing the Perspectives
| Perspective | Primary Goal | Fear | Accusation toward the other |
|---|---|---|---|
| Left-Pathers | Speed to Market / Profit | Wasted Effort | "You are gold-plating and over-engineering." |
| Right-Pathers | Resilience / Sustainability | Technical Debt | "You are hacking things together recklessly." |
I believe there is a middle path that both groups overlook—a concept that has been hiding in plain sight for years.
Defining the BSSN
The Best Simple System for Now (BSSN)—which we might call the "Bison" to contrast with the "yaks" we usually shave—is defined as:
The most uncomplicated system that satisfies the current product requirements, implemented to an appropriate professional standard.
This definition is precise. If you remove or alter any part of that phrase, the system becomes weaker. It contains no "just in case" code; every line is exactly as robust as the current situation demands—no more, no less.
1. The "for Now" Constraint
The most radical part of this philosophy is that the system must not anticipate the future.
For most developers, this feels wrong. We are trained to seek general solutions. We often find ourselves only one small step away from thinking: It's a rules engine!.
The Psychology of Over-Engineering
Our brains use pattern matching to save energy. Daniel Kahneman, in Thinking, Fast and Slow, describes two cognitive modes:
- System 1: Fast, instinctive, and unconscious (Pattern Matching).
- System 2: Slower, more deliberative, and logical (Seeing Reality).
When we see a problem, System 1 screams, "I've seen this before! Build a generic framework!" We believe we are being efficient by taking one big leap instead of several small steps. However, the art of BSSN is using System 2 to ignore the patterns and see what is actually there.
2. The "Simple" Constraint
When designing, I often experience a conflict between two versions of myself:
The "Clever" Me
- Possesses decades of experience and a healthy ego.
- Predicts exactly where the requirements will shift.
- Insists on adding interfaces, concurrent algorithms, and abstraction layers "just in case."
The "Humble" Me
- Remembers that every past prediction was
perfectslightly off. - Knows that "close-but-wrong" is the most expensive kind of mistake.
- Suggests waiting until the need is real.
The Cost of Speculation
When the future actually arrives, the "Clever" version's predictions usually fail in subtle ways. I then find myself:
- Fighting against my own incorrect assumptions.
- Deleting complex code to return to a simpler state.
- Debugging interactions caused by unnecessary complexity.
I have seen countless codebases filled with "speculative seams"—interfaces and hooks created decades ago for 20,000 concurrent users, while the system actually only serves twelve.
Code Comparison: Speculative vs. Simple
Speculative Approach (The "Clever" Way):
// Over-engineered for a future that may never happen
interface IProcessor {
process(data: any): Promise<void>;
}
class ConcurrentDataProcessor implements IProcessor {
async process(data: any) {
// Complex concurrency logic for "scalability"
return await Promise.all(data.map(item => this.handle(item)));
}
private async handle(item: any) { /* ... */ }
}
BSSN Approach (The "Humble" Way):
// Simple, meets current needs, easy to change later
function processData(data) {
for (const item of data) {
handleItem(item);
}
}
Conclusion: Simplicity as a Variable
The most vital lesson I've learned is that simplicity is a function of the present moment.
As the requirements and constraints evolve, the definition of what is "simple" evolves with them. Perfection isn't about building the final version today; it's about building the best version for now.