Great Software Isn’t Built To Last, It’s Built To Die Gracefully
How much can you learn from your code and how easily can you replace it with something better?
As software engineers we have this ideal of a perfectly designed piece of software that scales really well, requires no maintenance and lasts the test of time. It never works out.
Masterpieces get vandalized by a variety of changing requirements eventually resulting in a mangled pile of legacy garbage. We cope with it for as long as we can until it becomes so unbearable that we decide to destroy it and rebuild it from scratch. This time we commit to “building it right”.
And the cycle continues.
Legacy code was once cutting edge
Every good developer I know shakes their head in disapproval when they look back at the code they wrote a year ago. They get an immediate urge to rewrite it. It’s not that they wrote terrible code back then, it’s just that there are much better ways of doing things today than before.
Try it: Look at the code you wrote a year ago. It’s probably terrible. If you don’t think it is terrible, you probably haven’t grown as a developer over the last year. I can almost guarantee you that there is now a better way of writing whatever you wrote a year ago.
This is not a bad thing, it’s what makes software engineering so exciting. A year from now, there will be better technologies, better patterns and better tools to do what you’re doing right now. The question is this: knowing that your code will become obsolete, is there anything you should do differently?
MVPs: You’re doing it wrong
When I read my colleague Avrum’s post: MVPs: You’re doing it wrong, it got me thinking about how that might apply to the code we write. When our industry shifted to Agile and the MVP approach, it changed the way product managers and designers approached their craft. However, for developers it just meant better collaboration and direct access to other disciplines. It didn’t actually change the patterns and practices we used in code.
The whole idea of MVPs is about focusing on what you know now, what you want to learn and iterating accordingly. This approach can and should be applied to the underlying engineering as well.
Don’t build for the future, build for future change
First, you need to come to terms with the grim reality — the code you are working on right now is going to die. It has to die. And it will probably die within just a couple of years or so.
Now ask yourself, how can you make the most of those couple of years and what can you do to make sure it dies gracefully?
This is ultimately about optimizing your code for learning and change: How much can you learn from your code and how easily can you replace it with something better?
Monitoring over scalability
Instead of trying to make sure your system will scale, focus on learning where and why your system doesn’t scale. Monitor performance and discover the bottlenecks — don’t try to predict them.
Test behaviour, not implementation
In my experience, the biggest hurdle to changing existing code has been burden of fixing or re-writing a ton of broken tests. It drives me nuts when I change the implementation of a function or class while retaining the same behaviour and a bunch of unit tests break. This is often caused by unit tests that are too low level. Find the right altitude for tests while being able to provide good coverage, execute quickly and remain stable. A good test suite should encourage refactoring, not hinder it.
Keep error logs squeaky clean
Every error reported by your application should be a real error — something you need to act on right away. If that’s not the case, suppress the error (or drop it down to a lower level) and log a ticket to address it later. If you let the errors get out of hand, the log will eventually get ignored and render itself useless. An error log that you can trust is immensely beneficial when refactoring code or pushing significant changes to production.
Write less code
It seems obvious, but it’s so tempting to break this philosophy. Don’t build features unless you’re damn sure you need it.
We’re building an API. We should probably add pagination?
Not necessarily. What if our product doesn’t need anything more than the 100 most recent results right now. Put a hard cap for now. By the time we need pagination there may be a better way to do it than we know now.
Evolution — not death
Optimizing for learning and change is only worthwhile if you actually leverage it to constantly evolve your application. Often, this is driven by changing requirements in the product and new feature development. Use this opportunity to kill off or replace parts of your application. If you’ve built your application to die gracefully, this should be a breeze. Eventually you’ll find that your application never really dies, it just gradually evolves into something new and better.
If you found this post worthwhile, I’d appreciate a ♡ below. You might also like The One Question I Ask In Every Interview.