In my journey as a software craftsman, I’ve learned a few things. One of them is that software is an art – and an aspect of that art is the interesting tension of serving two distinct audiences. Audiences whose interests sometimes, but don’t always, align. I’m talking of course about the audience that puts your software to use, and the audience of builders that will come behind you and make your software do new things.
In the normal world of software – the second audience is the one most neglected. Serving this audience, though, is the measure of true craft. It shows that an individual has developed the ability to hear the quiet impulse that has driven the greats of the past to their heights of achievement – the lust to build something great, just because we can. And it shows an ability to think beyond the instant gratification of a pat on the back from a boss, or relieving the pressure of a driven project manager.
The other thing that I’ve learned is that, as with any art, finding the underlying principles is generally a matter of trying a thing, observing the aesthetic quality of the result, and then judging based on that if you should use that thing again in the future. The thing you try may come from a flash of your insight – though – as I like to say – good artists borrow, great artists steal (which, incidentally, I’m pretty sure I stole from someone).
Using that approach, I’ve applied the SOLID principles to my art and I’ve discovered an intensely aesthetically pleasing result – in terms of just looking and reading the resulting code, and the ability to adjust it and modify it as situations and needs change. These results both directly apply to the two audiences mentioned above.
Recently, a wise craftsman brought an interesting aspect of one of the SOLID principles to my attention. He pointed out that with regards to the Open-Closed Principle, if a class exposes a public member variable it is necessarily not Closed in the OCP sense. This is because the purpose of a member variable is to be used by methods to keep state between method calls. That is to say, the purpose of a member variable is to alter the behavior of a method — so the behavior of the method is no longer pre-determined – it can be changed at an arbitrary point in time.
Now technically, if the class, and more specifically the particular member variable, is simply acting as a data, and there is no behavior dependent on the member variable then changing it doesn’t alter behavior. But, practically speaking, the expectation of member variables is that they’re used by methods in the class.
I had personally always thought of being “closed for modification” to be meant in strictly a compile-time sense. That is, “Closed” referred specifically to source code. But as I thought about my friend’s assertion more, a question occurred to me. What is SOLID good for? Well returning to what I arrived at in an experimental fashion – it is good for making source code aesthetically pleasing and easy to change. And then a second question occurred to me – how does OCP contribute to that? It contributes by allowing the reader to ignore the internals of existing code, which brings focus to the overall structure and to the overarching message that it is communicating. This is artistically more compelling and it makes the overall code-base easier to understand.
So I would suggest that changes at run-time as well as compile-time are important to eliminate in this respect. And as such – OCP does in fact include run-time closure in “closed for modification”.
Having this settled in our mind – another interesting question arises. Does “encapsulating” the change of the member variable by making it private and only modifying it through a method call make the class “closed”? There are two differences between setting a member variable directly, and encapsulating it. The first is that you don’t actually use an assignment operator. But this doesn’t do anything to eliminate the fact that you’re changing the variable. And the second is that you might potentially limit the values that the state may take on, and thus you potentially have a better idea about the nature of the behavior. While this may be true, the fact that the state can change at an arbitrary time means that the internals of the class can no longer be ignored – since a given method may have more than one potential behavior. This means that we clearly don’t have a “closed” class.
To take this just one step further – another thing that I’ve discovered is that the more SOLID I make my code, the more my OO code looks very much like FP code. Because of this, I’ve said for some time that the two paradigms are converging. This has been based primarily on the experimental approach I’ve talked about here. But if we look at this situation with OCP – what we’ve basically shown is that a class isn’t SOLID if it maintains state (again, barring strictly-data types from this discussion). A class with just behavior is very close to being just a namespace with a set of functions in it.
All this being said, I believe even more strongly that the paradigms are converging. Furthermore, I’m fairly convinced that there are underlying principles that dictate this. Both paradigms seek to make code “easy to reason about” (to use the FP mantra) though they come at it from different angles. But in the end, they’re shooting to engage the same mechanism – our human instinct to reason – in the most efficient way possible. After all, what’s more aesthetically pleasing than that which fully engages the instincts it targets in us.