Developers: we’d all suffer a lot less if we weren’t so dang clever
A clever solution is the decision to introduce novel patterns, uncommon workarounds, and/or additional layers of abstraction to solve an underlying problem.
Many of the tools we use daily were once clever solutions, so I want to stress that a clever solution is not automatically a bad solution. The problem isn't that developers are clever.
The problem is that developers want to be clever, so they tend to reach for clever solutions first, introducing unnecessary complexity and overhead when a simpler solution is available. Developers love hard problems. It's exciting to stare down a challenge, break it down, and — using nothing but our super smart brains and Stack Overflow — create a new and exciting solution to the problem.
Clever solutions are a huge risk for creating knowledge silos, technical debt, process bottlenecks, and organizational sluggishness.
Clever solutions come in many forms
In my career, I've seen some doozies when it comes to clever solutions that started out exciting and ended up costing us more time and stress than they saved.
- a custom content management system because "WordPress is too bloated". It did less and sucked more and I eventually convinced all of my clients to use WordPress instead.
- a bespoke query language because the existing solutions don’t support some esoteric edge case.
- hand-rolled functions from well-maintained open source libraries for... reasons?
- an "all-in-one" abstraction over all the business logic of a 30-team application.
- a "massively scalable" generic approach to data manipulation used exactly once in the codebase. What we needed to do was get values out of an object. What we ended up with was a schema definition file and generic
I've seen teams craft fully customized build systems (that no one but the creator can get to work), devilishly clever caching strategies (that everyone works around because they don't understand them), data normalization layers, proxies to do a bit of business logic so they aren't blocked on the backend devs, and so, so many more clever solutions.
These projects start with best intentions.
They end as Byzantine legacy code that frustrates and slows down everyone on the team.
Kat Maddox illustrates in a tweet what I’m trying to explain in 1,300 words.
Clever solutions come with strings attached.
When you invent solutions, you gain the freedom and flexibility to do anything you feel is necessary to solve a given problem — but you also take on a ton of work to keep it healthy after it is deployed to customers.
Clever solutions require ongoing maintenance, support, and development. Now you need to maintain, support, and evolve that codebase to keep it functioning.
Clever solutions create a training and documentation burden. Customized, clever solutions make it harder to onboard new developers, and often results in knowledge silos. This feels bad for the team and worse for the developer who's about to get paged in the middle of dinner because they're the only one who can unblock the company.
Clever solutions create liabilities. If the creator leaves, the clever solution is now legacy code no one understands.
Each of these obligations fight for the “actual priority” spot on the priority list.
Before long, the context is lost and the company just took on more tech debt. Dealing with tech debt often leads to a new clever solution, and the cycle continues.
We need to think of downstream effects when designing solutions.
Programming is an interesting career because it can be both a career and a hobby.
I fall into this camp. I love puzzles, so teasing apart a complicated problem hits me right in the dopamine.
But it almost always feels like a chore to learn an existing system or tool.
It's vital for long-term personal, team, and company success to learn how to recognize when optimizing for fun will end up destructive.
We need to stop implementing clever solutions as a way to avoid learning how other people's code works. Instead, we need to look for great solutions that set us up for success now and in the future.
Great solutions reduce complexity and overhead.
In my career, many of my biggest wins involved removing huge chunks of complexity in favor of a straightforward system that could be maintained by developers at any experience level.
Removing complexity increases the team's ability to reason about the codebase. This makes it more maintainable, which makes it less of a bottleneck.
This is one of the reasons I'm so bullish on the Jamstack architecture.
By moving to a precompiled, decoupled architecture, I get to remove the complexity of running and deploying servers. And by choosing a platform-as-a-service provider to handle the building and deployment of the frontend, I also get to offload all the overhead of managing that process — it becomes automatic. (I'm a little biased because I work there, but I use Netlify for this.)
The best solution is the one everyone can understand.
Solving a problem in a way that the whole team can understand, maintain, and extend is far more valuable than a solution that is computationally clever but opaque.
Our assessment of how effective someone's solution is can't stop with the initial delivery; it needs to include ongoing maintenance effort estimates and onboarding costs. Looking for established tools and platforms — and the documentation, maintenance, and community support that comes with them — is a great default action. Building something custom should rarely be the first choice.
Cleverness as a last resort.
There will absolutely be cases where the problems we're facing are truly unique and the solution can't be simple.
Our challenge is to thoroughly weigh the trade-offs and make sure that we're only choosing the clever solution when something simpler isn't an option, rather than immediately chasing clever solutions because it's more fun for us.
Building the skill to be discerning about when and how we deploy our cleverness can be the difference between a maintainable codebase and a crushing pile of tech debt.