We programmers have many tools at our disposal and lofty concepts we can use — but that doesn’t mean we should.
Abstraction, I am often told, is “the core concept of programming”. It is true that without it, we would be writing software in byte code, scratching our heads and looking for new careers. Abstraction gave us C. It gave us all manner of languages and concepts with which to express ourselves — as is each programmer’s right. However, some programmers shout ‘abstraction’ from the rooftops and peddle it as a salve to all programming woes and I get all fidgety.
The problem, I have found, is not abstraction itself but that the word is sometimes used to mean: indirection, hidden behaviour and additional complexity — though they will tell you it makes everything so simple. And who could blame them? We have lots of buzzwords to help the budding abstracter like: interfaces, inheritance and encapsulation. Unfortunately, just because a child can reach the scissors, it does not mean they should always use them.
For example, when I was new to programming, I was preoccupied with interfaces that could be ‘swapped out’ and modelling problems using dozens of classes with hidden, volatile state.
If you think you’re writing software that will survive, unchanged and perfectly functional for decades, you’re probably wrong and I’m sorry. If you do work on that project, then you are likely to make some poor programmer’s life a living hell as he’ll be booting up Windows XP or Internet Explorer 9 in 2030.
People like to use interfaces because: what if?
“What if we want to swap out the rendering library in the future? It will be easy because it’s hidden behind this handy interface.”
If you’re implementing features and making these kinds of decisions then you’re not implementing features — you’re wrapping yourself in a comfortable blanket of speculation. Not once have I seen this decision pay off. If you do end up changing technology, your interface, invariably, won’t be quite right and things will need tweaking. If you have to tweak an interface and its callers, then you may as well have not bothered. So don’t bother.
Another temptation I started out with, was to build a tower of classes with internal, hidden state and behaviours — to map my problem to some more tangible concepts rather than deal directly with the complexity of the data.
It is a vice that piggybacks on the misconception that encapsulation is king. Encapsulation is court jester at best. As software grows more complex, it becomes harder to reason about the interaction between these types of objects within your application, making it harder to debug. Future programmer’s — including you in three months — will struggle to reason about what your software actually does and how. It is telling to me that abstracters often balk at making small changes or quick fixes as software grows.
I am not saying ‘don’t use the tools that OOP introduced’, I know some good programmers who do and from time to time it’s necessary. I am, however, suggesting that you implement exactly what is required and nothing more. Software is just data: input, processing and then output and my best code has always been that which does the simplest thing to get the job done. My more recent stuff is also mostly functional, but more of that another time. For now, you might enjoy this talk from Mike Acton on data-oriented design in C++.