I read a blog by Brandon Savage a couple of weeks ago and it triggered some thoughts. He refers to a blog by Marco Pivetta which basically states "Final all the things!". Brandon comes back with a more mild opinion where he offers the notion that this approach might be overkill. Since both posts got me thinking I tried to organise my thoughts on this in the following post.
I've almost never finalled anything. I never thought to start doing this and in if I've written final more than 10 times in the past 5 years it would surprise me. This is not because I do not think it has no merit, it is simply that I've not felt the pain of not doing it.
After some thinking I realised I did feel that pain. I do not miss the keyword final but I could sure as hell do without a lot of the occurences of extends. I will not say that extension is bad by default, but I think there could be a lot less of it and I think it would make our lives a lot easier.
It does not happen all the time, but I know of some places where extending led to some very curious solutions because widely different concepts shared the same base class because they could use part of it. I don't know if a final would have changed anything here, but it is certainly cheap to try.
Why final is a good idea
Basically, for all the reasons Marco Pivetta states. More specifically, these points in my eyes are why final won't hurt me while it might help:
1. Making something no longer final is easy, the other way around is usually harder (because implementations and extensions already exists)
Starting out a practice at 100% and loosening it once you have experience always seems better to me. I'm in no way a zealot on anything, but I was very strict on rules of scrum when we started practicing scrum about 4 years ago. It is so easy to come up with reasons up front on why stuff will fail or will be hard but you need to experience it before you really know. Think of small controlled experiments.
Letting go of a specific scrum artifact, because you experienced it is not working, is easy. Removing a final from a class is also easy. Adding final to a class that is already heavily used and extended is a lot more complicated.
2. Brandon Savage makes a point where he states that the original Open closed principle deals with external API's and not internal ones. He also states that When C extends A, A's internals are C's internals (that is what I interpret at least). All this is right.... and unimportant. If you change A you stll need to change C (and D,E,F,G, etc). Brandon says:
When inheritance takes place, you have not created a contract between two objects; you’ve instead created a new single object.
I feel you did create a contract. It may not be formal, it may not be set in stone, but when extending A you are able to use internal methods of A. You will use them even, since extending would not be of much use otherwise. This means an understanding of the workings of A at a certain point of time. A contract if you will.
I think it boils down to the fact that although the Open/Closed principle might originally have said that internals are open for modification, that does not mean it is always a good idea.
We do not follow rules so we can adhere to a principle, we follow a principle because it makes good sense in a specific context.
I will start to define classes as final for myself, just because I'm curious what the effects will be. Given the fact that removing a final keyword is easy this is a cheap experiment that might prevent my future self from making a mess out of code that I will write today. Worst case? It doesn't....
I think Brandon is correct when he says that extending doesn't inherently create a contract. That's what interfaces are for.
The point is that it might not be inherently, but effectively you did create a contract. If you go around changing classes that are extended, you're at risk of breaking expected behaviour of extending classes.
There is no contract, that is the bad part of it. By extending you act as if internals of the class you extend are a given, but they're not. The lack of an inherent contract is exactly the problem.