What we talk about when we talk about simplicity

Juan Álvarez
Podio Engineering Blog
6 min readDec 10, 2015

One of the recurring topics at Podio is the simplicity / complexity of its conceptual model, user experience, and all the plumbing behind it. Among developers, this topic is always on the table.

But simplicity is such an abstract term, so overused, that I don’t know how we even understand each other. And well, it happens that sometimes we don’t understand each other at all.

“UNIX is simple. It just takes a genius to understand its simplicity”
Dennis Ritchie

Technically, some parts of Podio possess that kind of simplicity that is a pleasure to work with. Often the crucial bits. Developers before me applied some good practices to keep stuff simple, and now I benefit from it (thanks, folks!).

It makes sense that, when it comes to the big picture, technical choices are being carefully made. Laying out an architecture, balancing reliability, testability, extensibility, and a bunch of other ities… it’s hard. There are many trade-offs and constraints in play, so good programmers think twice before jumping into a rushed solution.

But what about maintaining simplicity? Complexity hides in the details. In the small features and bug fixes. In the move fast and break things, and the just ship it!. Things that are expected to be easy, and it turns out they aren’t because something we didn’t anticipate gets in the way.

So we say:

“Let’s do the simplest thing here”

Famous last words.

I’m going to use a familiar example to illustrate how this happens.

Consider the “like” functionality in a typical mobile or web app. There is a piece of content (e.g., a blog post), and next to it a like count and a heart icon that you can click to like the content yourself. If you do so:

  • The heart’s color will change
  • The number of likes will increase
  • Other people will know that you liked it

Easy, isn’t it? This is not one of those super cool challenges we see at conferences, or in Internet articles and job descriptions. It’s just a normal feature, everyday work.

This is how it goes:

“You have a sec?”
“Sure, what’s up?”
“The API operation to like things is PUT /like/thing_id and returns 204 on success”
“Yup. What’s wrong with it?”
“Well, when you like something you need a fresh like count on that object”
“Update the count yourself. It’s the simplest thing”
“If I do that we’ll be duplicating logic in the client and the server”
“Then fetch the whole thing from the server”
“Two round trips for a like? Too slow”
“Then tell the backend guys to return something useful on that PUT”
“That will probably break other clients”
“Ask them to create a new version”
“They’ll have to maintain and document 2 endpoints for a simple like… But I’m gonna ping them and let you know”

(10 minutes later)

“They won’t fix”
“Why not?”
“Because they want to keep it simple… on their end”
“Shit”
“Let’s go with 2 requests. If it is slow, it is slow”
“Not if. It’s gonna be freaking slow. People have terrible connections in their phones”
“It’s life”
“I still don’t get why you don’t do it yourself? It’s just an inc and flipping a boolean”
“What if the logic changes in the backend?”
“The logic of a like??”
“You never know. Worse things happen…” (shrugs)
“Nah. You shouldn’t even wait for the first call. Just update the UI state, fire all the requests you need, and then revert if something went wrong”
“What if the user leaves the screen right after liking something, and then the request fails? Then she’ll think she liked it when actually she didn’t…”
“That’s a veeeery corner case scenario. You’re over-engineering the whol — ”
“But it’s in the spec, isn’t it? The definition of like is that you cli — ”
“I get it. But let’s focus on the normal case, you’re adding a lot complexity because of a scenario that will happen once in 100 years”
“Ok. So you suggest that I do the trick only for the like or for every UI interaction?”
“Only the like”
“That will be inconsistent with the other stuff. I think is gonna confus—”
“We’ll iterate. We can’t embark on a project of this magnitude now”
“And what if users clicks two times too fast? I’d be increasing the counter twice…”
“You can disable the UI component after the first click so it’s not clickable until you get a response”
“So in the callback function I enable the UI component again”
“Exactly”

(thinks for a few seconds)

“Coupling the transport layer with the view feels like a hell of a mess for something that should be so simple. It’s just a like”
“Got a better ideas?”
“Maybe we shouldn’t do this at all. Tell users it’s too hard”
“Look. Let’s do the simplest thing here.”

So…

What would you do? What is really the simplest thing?

“Your simplicity is my complexity”

Changing the UI state before getting the server response is actually easy. So is making a button clickable when that response comes. But in 1 year, when your peers stumble upon that line of code, how are they going to know that you wrote it because there is some duplicated logic in the server and client for performance reasons but the server “version” is idempotent and the one in the client it is not?

Make it “easy” and you’ll end up with a bunch of meaningless if-else statements scattered across a dozen of functions and files, silently depending on each other, requiring more meaningless if-else statements to keep different pieces of state in sync, none of which will have anything to do with the actual task. Namely, liking some content.

So I think that simplicity is not about the time it takes to type some code. It is not about how easy it is to write, nor how easy it is to read. A code block can be readable, unit-tested, and used (in the IDE meaning of “usage”); and you still might not know if the logic it performs is applicable now (as opposed to the time it was written), or if it is safe to move it before or after some function call, or if [insert-favorite-side-effect-here] is going to cause troubles if you touch it.

It is funny that developers can talk about code written 6 months ago by the same team in terms of legacy code. It is complexity that makes legacy code legacy, and not its age or careless former programmers.

Complexity accumulates as fast as you stack up small features on top of each other, when you write them without thinking about what knowledge your code is conveying. It grows even when you clean up old complex code, because you’ll forget to remove some bits of behavior here and there that will clutter the meaning of the code for the rest of its days. And it undermines the understanding of it, causing the next feature you write to generate more complexity.

Nevertheless, avoiding code complexity is easier said than done. Often it comes from a business logic that uncontrollably introduces similar concepts vaguely related, and you are left with lots of slight variations of the same thing. Then you feel that instead of building a cathedral out of composable Lego bricks, you do it out of the arbitrary shapes of lost Tetris games.

And when it is not the domain, it is a pile of technical nuisances, like ad hoc hacks for specific browsers and devices, external dependencies, SLA’s, security policies, slow networks and countless other monsters awaiting an opportunity to smash your beloved and fragile simplicity.

Illustration by Joy Leelawat - http://joyobject.org/about/

I like to see simplicity that way, as the last fortress repelling the attacks of ruthless creatures.

Because simplicity is not only desirable when you design an architecture. You need it all the way down to the the last line of code, and fight hard to preserve it. Everyday work.

--

--