Every new line of code you write comes at a price: you need to maintain it over the time until you decide to replace it with a new fresh bugged code.
In a desperate attempt to avoid it we start speculating over the future by making the code as generic as possible.
When possible code must require a low cognitive load to be understood. Over-engineering is a particular type of complexity where developers have made the code more generic than it needs to be, or added functionality that isn’t presently needed by the system. As reviewer you should be especially vigilant about over-engineering to limiting future speculation.
When about one year ago, with my team we started working on a complete rewrite of our company software we decided to follow three pillars: readability, modularity and reusability.
It’s almost easy to make reusable code when you perfectly known your domain but even in these cases you are not free from making mistakes.
And they happend, of course.
Few weeks ago we started working on two new big features in our project and we decided to share portions of the code between them.
In addition to some other conjunctions (good for another post) we didn’t know exactly every spec, moreover the product team changed idea during development.
The result is a failed attempt to create reusable code in the first place.
This post is a small lesson I learned from both these episodies from tech perspective.
While shared code is the mantra of every pro developer, sometimes we are too focused aiming to accomplish this myth we just forget the long term price.
It’s all about reusability. Or not?
Modern software is all about reusable patterns: reusability is a common strategy to amortize the price of development with the ability to maintain it over the time.
The problem with reusable code is that it gets in the way of changing our mind later on.
As your software getting older and changes accumulate, managing how the code fits together became a significant task. The more consumers of an API you have, the more code you must rewrite to introduce changes; the code which rely from other APIs must be adapted too.
Maintenability is getting harder.
Duplicated code may help you
Building reusable components is easier to do in hindsight when you have two or more concrete example in your code, reather than speculate in advance when you may don’t know enough about the domain or any side effect.
Sometimes a little redundancy is healthy: don’t blame yourself if you copy-paste code a couple of times. An incremental approach is far better than following the crazy idea of the perfection: a software is like a living creature and is perfectly fine to evolve it incrementally instead of writing tons of generic code speculating it will be fit a unpredictable future.
Once you make something a shared API, you make it harder to change. Rather than making a library function, just to get a handle on how it will be used. You will have time and knowledge to make it better later.
Get inspired by evolution
Unwavering incremental change can create remarkable and monumental result Ryan Lilly
Think about evolution: it’s is an incremental game where new “code” is added roughtly to resolve a problem or make an improvement.
If the time prove the goodness of the solution then it will iterate to better handle other cases and smoothing over the edges.
Once you’ve copy & pasted something enough times, maybe it’s time to pull it up to a function or make it generic.
You are now sure you really need of it (we will call it hard-to-delete part) and you perfectly known how to fit it in your layered infrastructure.
This approach allows you to separate the code trying to keep the hard-to-delete parts as far away as possible from the easy-to-delete parts.
A secondary effect of this approach is we’re trying keep the parts that change frequently away from the parts that are relatively static.
Minimising the dependencies or responsibilities of library code, even if we have to write boilerplate to use it.
This separation will bring out a clear vision of the core layers of your system; you will can delete parts without rewriting others is often called loosely coupled. You cannot always have this overlook in the first place.
A healthy codebase doesn’t have to be perfectly modular. A healthy code base has some verbosity, some redundancy, and just enough distance between the moving parts so you won’t trap your hands inside.
Code that is loosely coupled isn’t necessarily easy-to-delete, but it is much easier to replace, and much easier to change too.
As said in its “Write code that is easy to delete, not easy to extend”:
Good code isn’t about getting it right the first time. Good code is just legacy code that doesn’t get in the way.
Good code is easy to delete.
If you want to go deeper with this topic I’m suggesting the following links.
Feel free to comment this post on twitter with additional links.
- The Little Manual of API Design, J. Blanchette.
- How To Design A Good API and Why it Matters, J. Bloch.
- Wirte code that is easy to delete, not easy to extend, Programming is terrible.