Cleverness is seductive and it’s easy to mistake for skill. But over the years I’ve watched the same thing happen across team after team: each time a new piece of “cleverness” got introduced, it didn’t last long past the day the engineer who wrote it left the team. Talking to other iOS leads, I see that this pattern repeats.
The trap
We humans forget things easily. For example, how much effort it cost us to come up with an idea. Once it has formed, it often feels obvious. But:
- We forget that coming up with the idea caused effort, effort others will have to put in to reach understanding
- We forget we needed years of experience to come up with new smart ideas quickly, years not all contributors have
- We forget that a “clever idea” does not have to mean “clever execution”
There is a cost attached to those clever solutions. Everyone in the team will need to pay a mental load tax. It’s invisible, but it’s eating away your productivity.
A thing that was meant to help and make the codebase better can turn out to make it harder to work in. It may take more effort to read and understand any given code. Colleagues might even be scared to ask for explanation, because they don’t want to admit publicly that they do not understand the codebase they are paid to work in.
I have been in senior+ only teams where I asked how a thing worked and everyone shrugged their shoulders.
Why we still let it happen
We all have things on our plate. When someone decides to make a thing better, we want to encourage them and let them run. However, things move fast and suddenly we have that one component or abstraction layer on our hands that does not look beyond the immediate scope.
Once it is built and the PR is open: it feels too late to steer the conversation in another direction. Because only one person had the pain and did the work, critique quickly lands personally.
Nothing ever “lgtm”ed more than PRs that nobody asked for.
Case study, breaking down the cost
Few engineers actually price the clever things they build. It’s worth doing once, just to see the shape of it. Here’s a small example from a project of mine. Small on purpose to illustrate how quickly things add up quietly.
Context: We were a team with 4 senior and 1 junior iOS engineers. We found a bug that made us realize that a certain API we relied on had limitations. The easy way to go would have been to be more strict with how we use the API. One engineer instead wanted to build a wrapper to handle the magic and we could remain more flexible at the call site.
Fast forward to a few months later. Said colleague moved to another team and we had (yet another) issue with the same thing. The solution was axed and we decided to just be strict with how we use the API.
Concrete items on the bill that are easy to put into numbers:
- Building the wrapper: 2 days for 1 senior engineer
- Reviewing the wrapper: 3 hours for 1 senior engineer
- Applying review suggestions: 1 hour for 1 senior engineer
- Identifying and fixing bug 1: 1 day for 1 senior engineer
- Reviewing the bugfix 1: 0.5h for 1 senior engineer
- Identifying and attempting to fix bug 2: 2 days for 1 senior engineer
- Team discussion on how to proceed: 30 minutes blocking 5 engineers
- Removing component and updating call site: 1 day for 1 senior engineer
- Reviewing the removal and call site update: 2h for 1 senior engineer
Assuming a senior iOS engineer in central Europe has a salary of 90.000€ and costs the employer 120.000€ (social security, equipment, HR overhead, …), the company pays about 70€/h to have that senior on staff.
Total: 57h, roughly 4.000€. For one single small component. In the grand scheme of things, it’s not that much for a company. But nobody would green-light that spend for a minor improvement.
And we also have hidden costs that are harder to put into numbers:
- The time spent on that solution was not put into features that generate revenue.
- The time spent on fixing those things needed to be done by an expensive senior. A “we forgot to be strict with the API here”-bug instead could be easily fixed by a junior.
- Additional bug reports came in and needed to be handled by the PM.
- Another thing that required onboarding and project knowledge. “Don’t use x directly, use our wrapper instead.”
The project grows, more of those components get created, some of them grow deep roots in the project and both get hard to manage and to remove.
When clever does work
There is an important distinction to make: “clever ideas” do not mean “clever execution”.
An example:
- a clever idea might be to add another abstraction layer so things above won’t need to worry about certain specifics
- a clever execution might be to add a minimally invasive change to an existing layer that defers control for the few components that need it
The difference is the following: adding an abstraction layer will spread its complexity throughout the app. It is a layer. A layer covers the entire span of its scope. A layer is meant to not be skippable. Everything has to pass through, even if it’s not needed in most cases. A targeted change, however, may make the existing layer a bit more complex, but it’s isolated complexity. That addition will not be relevant to most of the code and thus can be ignored in those contexts.
Clever works when it’s used to explicitly keep complexity in check. “Wouldn’t it be cool if” is not enough. “Make things nice” is not enough.
It’s always easy to make components more complex later on. It’s never easy to make a thing less complex.
Nobody ever complained about tech debt in simple components.
What coherence buys
Once I led a team building an E-commerce app for a Series B+ scale-up. Shopping itself was handled natively, but the checkout needed to be solved via a web view for a long time. When it was finally time to build the checkout natively, I was not part of the team anymore. What remained was one lead engineer and a junior.
In the planning meeting the PM asked the lead how much effort he’d estimate. The conversation went like this:
Lead: “Two weeks. One sprint.”
PM: “hahahaha. No, come on, now, seriously.”
Lead: “No, I am serious. One sprint.”
It was done within a sprint. Front to back. Implementing APIs, implementing designs including forms for payment and address input, handling error cases, writing tests. Plus time to spare for thorough testing. One lead and one junior.
The secret: there was nothing special going on.
The junior didn’t need carrying. And at that time he hadn’t been long on the team either. He was able to look at the code and see what patterns he should follow. That’s easy when there are few patterns present that need to be decoded. The lead had the room to decide what to build and the junior was able to hold the pace.
Boring has a bad rep. Boring is seen as bad. What it actually means: unsurprising. Works as advertised. It’s accessible to everyone. When something needs doing, it’s clear what needs to be done where and in which way. It means you don’t need to think a lot. You can focus on doing the thing instead of the ceremony around it. Humans have a context window as well.
Optimize the project for somebody else to read it. Create a codebase where everyone feels comfortable going into any corner. Where your engineers feel at home.
You will end up with a codebase with faster onboarding. A codebase with a better bus factor. A codebase with more maintenance resources to be spent on the real pain points. With happier engineers. The advantages compound over time.
The best engineers don’t write code that makes them look smart. They write code that makes everyone around them look smart. No clever layers to decode, no one single person holding keys. Just a codebase that quietly lets the next person in.