Stop oversimplifying simplicity

Mar 13, 2024 #engineering #principles #teamwork

The true measure of a staff software engineer is not how simple their code is, it's how they help their team navigate hard problems.

Here’s one of my least favorite images:

          An image is shown. The image is a line chart that shows that as your years of programming progress, your Code complexity goes up until it reaches a plateau, and goes back down to “super simple code” as you become a programming expert. The peak has an arrow pointing to it that suggests that when you’re at your worst complexity, you’re building abstractions and interfaces.

This image is terrible because of 3 reasons:

  • It's true
  • It's subjective
  • It's irrelevant

I'll break down why this all bothers me.

The image is true

There's a classic software engineering career progression joke that it reminds me of: A Junior engineer repeats their code. A Senior engineer lives by "Don't Repeat Yourself" (DRY). A Staff engineer... repeats their code.

Are staff engineers lazy, or is there more to this pattern? Allowing for code repetition is a reasonable default: Extensible code is reused less often than anticipated and is far more ephemeral than we'd hope. Even when an abstraction is useful, its best form isn’t often revealed until sufficient repetition exists to be abstracted. This is where the rule-of-thumb “Write Everything Twice”, or “WET” comes in: don’t bother to build abstractions until you’ve passed a threshold of usefulness. Engineers learn these habits over the course of their career, through many yak shaves and deleted codebases.

Yet… it’s subjective

Since habits like WET are built up over the course of a career, one engineer’s "simple" is another engineer’s spaghetti code and a third engineer's unnecessary abstraction. This forms a spectrum of abstraction.

The Spectrum of Abstraction, on the left, spaghetti code, on the right, unnecessary abstraction. A person moves back and forth, and relative to them moves arrows describing everything to the left and right of them as wrong
The Spectrum of Abstraction is relative: Wherever you are, everyone to the left and right of you are wrong.

I've seen engineers be only slightly different in values, and view everyone to the left or right of them in this spectrum to be completely unacceptable.

One example of this is when folks debate whether a class should be broken down further (e.g., for Single Responsibility Pattern). Often both sides are right. On the one hand, Single Responsibility Pattern really does make code more testable. On the other hand, if you fail to identify the correct seams of individual responsibilities it can lead to tightly coupled "abstractions" with ultimately higher code complexity. The fact that these two truths can be correct at the same time leads to debating, refactoring, and negative feelings. Understanding these tradeoffs isn’t simple, but the outcomes are. Simple code doesn't always look the same, but it is easy to adapt when it needs to be, and easy to ignore when it needs to be.

The "super simple code" graph denegrates expert knowledge in favor of a idealized simplicity, and I think what bothers me is that this feels like an excuse to not draw from your wisdom. In my experience, situations where I was confident that my choices would simplify a problem were made easier when I was able to describe why the decisions were more suitable than the alternatives. (E.g., even if I don't completely refactor a class, being able to describe what it would look applying the SOLID principles has helped me avoid Yak Shaves)

It’s irrelevant anyway

But look, none of your code is reusable if your teammates aren't able to work with you.

        Character 1: 'I applied dont repeat yourself to my icon cache with a really cool abstraction'

        Character 2: 'huh, did you know, I built us an icon cache two years ago?'
        Bottom: The team discovers the principle 'don't repeat ourselves'

This whole debate about simplicity results in a culture war that prevents more experienced engineers from shipping code (because they're constantly fighting back complexity) and more junior engineers from feeling psychologically safe (at the risk of appearing too "complex"). None of this matters if it slows the cogs of change so much that you're not able to work together with your team.

So what do we do, then?

My advice is to not center code quality conversations on whether or not an engineer is tough enough to avoid using "visitor pattern" or even how many years of time someone has spent coding. Instead, help your team think about super simple code as the outcome. Ask the question "are we focused on the most important problems?". Even when folks disagree with an abstraction in principle, it's hard to care after the code is deleted due to a product deprecation or acquisition-and-merger. Simple code comes from understanding that some decisions don’t need to be made right now and are often better deferred.

This whole topic reminds me of an episode of the Hidden Brain podcast. The episode describes studies that showed that the secret to great teams isn't years of experience or being really smart, but collaborating well together. We should focus more of our effort on pursuing a shared understanding of how to ship the right code and remember that we still have to work with our teammates who are in different places in their engineering careers.

In the end, having abstractions worth debating means our code is sufficiently long-lived. It’s a responsibility I’ll cherish, at least until the chat bots take it away from us. Remember, all code is ephemeral on a long enough time scale.