I have been developing software professionally for 4 years now, and in that period my own perception of how to write code has changed quite a lot. I’ve prepped a little story and maybe you can relate to some of it or maybe even the entire thing.
Story Time
Think back to when you just finished studies and were ready to enter the enterprise world. Every business problem sounds like a nice abstraction waiting to happen. Given the fact of freshness, you recall all the best practices that you need to apply when writing code. You ensure that the code adheres to the SOLID principles, that it is highly optimized, so that only the bare minimum instructions that are required are being executed by the CPU, and many other best practices that you just can’t wait to apply.
You see where this is going?
The smart engineer that you are let’s you quickly figure out 20 cases that this implementation must handle in the future, so it is easier to pay the price now, and then not need to change anything going forward.
You then release this awesome implementation into production and everything works just fine and the business is happy with the functionality.
A few months pass and the product owner sweeps by:
“We need to extend the solution you’ve built with these new requirements and patch some corner cases.”
You think to yourself, that should be easy, since you remember that the initial implementation was built to be highly adaptable and able to cope with ever changing business requirements. You start by looking at the code and realize, Holy shit, this code has a lot of layers and it’s quite hard to figure out where you should make the changes.
You faintly remember that you’ve added unit test to the code in order to validate the implementation. The tests provide some insight into what you were thinking back then and how everything should work, but when you look at the implementation it gets quite hard to identify where it is easiest to modify the code in order to cope with the new requirements. You slowly start to realize that the initial solution you engineered covers a lot of cases that in practice never were relevant and that these decisions had an influence in the way you designed the system. Short You over engineered the shit out of the initial problem. You find smart one liner ternary expressions, deep inheritance, even some arcane reflection that automagically wires everything together, etc. It all made sense when you built it (of course it must have, otherwise it would probably never have shipped), but now you face yourself (your past self) a few months later, and all that arcane wisdom you had, has long been replaced with new requirements and solutions to problems that are not relevant to this part of the domain. You find yourself in a reality were you wish that the past you would not have done everything in such an overly clever and over engineered way, but rather, written some simpler, dare I say, boring code.
Boring Code
The lesson learned for me is that sometimes it is better to write code that is easy to understand and modify (maintainability, readability and extensibility), even if it means sacrificing some of the cleverness that makes it feel elegant. It is important to keep in mind that requirements and priorities can change, and code that is too tightly coupled or overly abstracted can be difficult to modify. Writing simpler, more straightforward code can make it easier to maintain and adapt in the long run.
Now you probably think “Well that’s nice and dandy, but are you any good at practicing what you preach?”. Of course I struggle relatively often with striking the balance, because I am proud of my craftsmanship and want to strive for perfection, but that does not come for free. So putting aside my own ego can be hard, but just a few years of experience have already taught me, that on average it is a good call to do so, while still being able to approximate something that might resemble that perfection that I seek.
Funny aside (Appendix)
Actually when I was building this website I repeatedly caught myself in wanting to over engineer the solutions to some of the problems I was facing when building it. Every time I had to take a step back and think to myself “Is this worth the investment right now, or is it better to churn out some features, and then improve down the road”. I try to choose the latter and while doing so make sure that the code still adheres to some basic principles, so that it is not a nightmare to change when the time arrives.