A story about the risk of over-abstraction and false assumptions on technical debt.
A well-known scientist (some say it was Bertrand Russell) once gave a public lecture on astronomy. He described how the earth orbits around the sun and how the sun, in turn, orbits around the center of a vast collection of stars called our galaxy. At the end of the lecture, a little old lady at the back of the room got up and said: “What you have told us is rubbish. The world is really a flat plate supported on the back of a giant tortoise.” The scientist gave a superior smile before replying, “What is the tortoise standing on?” “You’re very clever, young man, very clever,” said the old lady. “But it’s turtles all the way down!”
The first time I’ve got in contact with this story is when I read the book Godel Escher Bach. Although it seems hilarious, it also points out that there is no solution for human stubbornness and lack of logical thinking. It is an exceptional example of missed credits for the inertia of mankind’s cumulative factual cognition, or perseverance of human-induced stories one could believe in.
Not soon after, I started recognizing some of these silly patterns in my own behavior. Of course regarding personal and behavioral stuff, but it somehow concerned me more that I noticed that these patterns can easily be found in day-to-day technical tasks. A recurring theme in my software seemed to be some serious over-engineering with abstractions over abstractions, all to separate concerns wherever possible and isolating whatever could be isolated. The layers of abstraction grew so thick that they became very hard to follow for anyone else including my future me, and I realized I needed some serious re-prioritization of what I perceived as good practice in software development.
The rule of three
It is so, so hard to leave technical debt when you’ve had a history full of it. This becomes the second nature of a developer. Remove any tech-debt up-front before it bites you in the ass afterwards. But there’s a risk in this.
We tend to prematurely optimize our code. But the risk here is that we optimize without knowing the full set of features that are required. This is when I introduced the rule of three (which sometimes is ignored, sometimes the rule of two, but don’t tell anybody okay?).
Only when you’ve seen similar functional demand occur three times, start clustering the functionality and isolate the individual concerns.
By the time you’ve implemented a third similar functionality (which usually needs some adaptions to work in a specific situation), you can tell something about the environment the component should work in.
Set your KPIs
(For those who don’t know, Key Performance Indicators, the stuff that tell you if you are doing the right thing or should pivot your efforts).
This might seem strange, but make sure you set your definition of done straight before you start the development of new features. The definition should only encompass the creation of functionality. Not the how. Just the what. Don’t create elaborate structures, but try to get to your goal the fastest way possible.
Read your code, and let someone else read it (peer reviews). If it’s not clear what it does or how it works, it’s not good enough
- Usage of other modules (DRY (Don’t Repeat Yourself))
Don’t do work that’s already done
- Don’t implement features you don’t directly need (KISS, Keep It Simple, Stupid)
I guarantee you that the functions you consider nice to have but unused, will be the first to bring your code to a grinding halt.
You’ll need these KPIs! Because odds are that you won’t feel good – at all – about the product you’ve just delivered. There’s ALWAYS a better way to do things, and that shouldn’t drag your just created real-life value down. Satisfy your KPIs and feel satisfied. But watchful.
Take notes along the path of deliverance. Mark the project as Concept and MVP (although functionally you might feel you’re there, you can sometimes treat functionalities as separated products) and keep track of it. Observe all stuff needed in the future and observe if your suspicion of lacking features, abstractions and re-usage of code are right. If so, don’t be shy to become your own PO and create a story that removes tech-debt. If your relation to your usual PO is one that has trust in it’s fundament, he should respect this story as much as any other feature request and allocate time to remove this technical debt.
Apply validated learning
By waiting to apply all these abstractions, you enable validated learning (beautifully described by Eric Riess in The Lean Startup) to more or less scientifically confirm the future of the feature (the standard definition used in validated learning), but also the need and the focus of the future optimization.
Bottom line: You’ll spend less time, on stuff that get’s thrown away.
It’s not turtles all the way down anymore. It’s just a bunch of oddly stacked turtles on a ridge in some water on a planet.
What follows after this.
I’d still like to write a blog post about testing code. This article about levels of abstractions relates to that future testing blog post in so many ways.
If you’d like me to put some focus on that, let me know by using the poll on the right side of the screen!