Software development has its eyes set firmly on the future and writes off the past as tired. “What service mesh is hot?” “Should I use Go, Rust or Elixir for my next project?”
It is often worthwhile stepping back and reading something from software development’s awkward teen years. Any advice that has survived the gauntlet of a few decades has a lot to teach us.
The paper “A Plea for Lean Software”, by Niklaus Wirth, is a good example. The article, from the mid-’90s, describes a growing concern for the complexity of software and its impacts. It’s scary how little has changed, or in fact, how much worse the complexity in our lives has become.
In his essay “No Silver Bullet”, Fred Brooks coined the twin ideas of essential and accidental complexity. Essential complexity comes as a direct result of the problem you’re solving. Accidental complexity is an unfortunate side effect of the choices you make in tackling the problem. It grows like mold. Spreading due to hacks, bureaucracy and mistakes.
Accidental complexity attacks us from all angles, and it’s a full-time job to manage that complexity.
What strategies can we use to keep it at bay?
The trajectory of software is usually to grow endlessly. There aren’t (really) any physical constraints in place, so why not add feature after feature to the heap — after all, it looks fantastic on the back of the box.
Not every function holds the same amount of value. Some will be used religiously by every user, and others by none at all. We need to remember that dead functionality causes software bloat, and software bloat can come with a hefty cost.
Code rots. The more code we have the harder it is to maintain and to build upon. The end result is more bugs and slower software.
Let’s take the advice commonly attributed to William Faulkner — “kill all your darlings”.
Be brave, ignore the sunk cost, and throw out anything that gives no benefit. Keep software lean.
“You start coding, and I’ll find out what they want!”
Sadly, that quote is all too real. How often do developers run at a problem with a half-baked understanding of what to achieve? We need to be crystal clear in what we’re building.
Fuzzy requirements and unchecked assumptions increase muddle. When all the pieces come together they don’t line up quite right and need to be bent into shape. This usually causes more complexity.
Take a step back and understand the context. What is the real reason you’re doing the work you are? What benefits is it going to bring? What is the simplest route to achieve that goal.
It is good practice to architect for change. But there is a difference between designing to handle change and building functionality that might come in handy someday. The one allows you to pivot when change happens; the other creates technical debt.
Be careful building in those what-ifs.
Rather than dreaming about what your users want, give them something to play with and gather their feedback. If it works, great, if not then try again with your newfound knowledge.
Start with something simple that solves a problem and grow from there.
“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” — Gall’s Law
It is impossible to know everything beforehand. Take small steps and reorient as quickly as possible.
Trap complexity when possible. This is especially important in an organization with many teams. Have you ever worked on a feature that involved coordinating 3 or more separate groups? Even if it was a success, I bet the journey was painful.
It is both an architectural and organizational smell to have too many moving parts coupled together. If you pull on one part of the system and it drags in five others you probably have a bad architecture on your hands.
Letting information seep uncontrolled between applications ties them together where a change in one app means a corresponding change in its partner — a tangled web that becomes ever more difficult to work with.
Back-to-back leaky abstractions can amplify any problem. Rather than keeping complexity in its cage it has to be coped with downstream. The further away from the source of the problem, the more difficult the problem is to solve.
“… be conservative in what you do, be liberal in what you accept from others …” — Postel’s Law
By being conservative in what you do and in what you expose to others, you are not only saving your colleagues a whole host of trouble but are making it easier to pivot and adapt in the future.
Keeping complexity at bay is half the battle of any successful software project. Ensure you keep that complexity in check, or you may regret it down the line.
- A Plea for Lean Software, Niklaus Wirth.
- No Silver Bullet
- Systemantics: How Systems Really Work and How They Fail, John Gall.