12

For the purpose of writing a coding styleguide, how should final methods in software design be judged?

By final I mean the Object-oriented sense that a class that can be subclassed provides methods, and that class using special syntax to prevent subclasses from modifying certain methods.

In languages like Java, there are other ways to prevent modifications of behavior, such as making methods final, or using static methods (where applicable).

As an example, this styleguide has a section "No final methods or classes" https://doc.nuxeo.com/corg/java-code-style/

No Final Methods or Classes

This hinders reusability. Nuxeo is a platform and we never know when it'll be useful to subclass something.

No private or Package-Private Methods or Fields.

This hinders reusability, for the same reason as above.

On the other hand Guava has classes with final methods, like https://guava.dev/releases/19.0/api/docs/com/google/common/collect/AbstractIterator.html

The JDK (Java) has some classes with very few final methods, like ArrayList, AbstractList, some with several final methods like HashMap, and some with many final methods, like AbstractPipeline.

Some people will say this relates to the Open-closed principle (Clarify the Open/Closed Principle), but articles on that topic usually do not talk about final methods.

Another angle is that this is related to the composition over inheritance design debate (Why should I prefer composition over inheritance?), since the idea may be to provide functionality via inheritance when considering whether to make methods final.

Somewhat related:

1
  • 15
    This reminds of a freelancer guy who once joined our team as a "senior Android developer". He thought that final was "for beginners" because "you know when you extend a class". The moral of the story: calling yourself a senior developer is as easy as uploading a styleguide to the Internet, but neither accounts for much. The reality is that being a framework makes final and private even more important, not less. Commented Nov 21, 2019 at 15:43

3 Answers 3

27

I think the arguments presented in Eric Lippert's blog post from 2004 Why Are So Many Of The Framework Classes Sealed? apply to "final methods" (in the Java sense of that term) as well.

Every time you write a method A (in a framework) which calls a non-final method B, A cannot rely on B on doing what it was originally doing any more, so A has to be designed way more robust than if B was final. For example, the implementation A may have to invest more effort into exception handling (when calling B), the exact behaviour of B and which constraints apply when overriding it has to be documented more precisely, and A must be tested more thoroughly with different overriden variants of B. Moreover, there must be invested more thought into the exact distribution of responsibilites between A and B.

In fact, in an abstract class of a framework, the way overrideable methods are used internally becomes part of the API of that framework (see example in the comments by @wchargin). Once the framework has been published to the world, it becomes significantly harder to change the semantics of those methods.

So this makes it a tradeoff: by making B final, you make it easier to create a correct, tested and reliable implementation of method A, and you make it easier to refactor A and B inside the framework later, but you also make it harder to extend A. And if a framework's implementation guide favors towards making nothing final, I would be highly sceptical about the reliability of that piece of software.

Let me cite Eric's last paragraph, which applies perfectly here:

Obviously there is a tradeoff here. The tradeoff is between letting developers save a little time by allowing them to treat any old object as a property bag on the one hand, and developing a well-designed, OOPtacular, fully-featured, robust, secure, predictable, testable framework in a reasonable amount of time -- and I'm going to lean heavily towards the latter. Because you know what? Those same developers are going to complain bitterly if the framework we give them slows them down because it is half-baked, brittle, insecure, and not fully tested!


This older question (and its top answer) from 2014 may serve as an excellent answer here as well:

In C#, methods are "final" by default (in the Java meaning of that term), and one has to add the virtual keyword explicitly to make them overrideable. In Java, it is the other way round: every method is "virtual" by default, and one has to mark them as final to prevent this.

The top answer to that former question cites Anders Hejlsberg to explain the different "schools of thought" behind these approaches:

  • the school of thought he calls "academic" ("Everything should be virtual, because I might want to override it someday."), vs.

  • the "pragmatic" school of thought ("We've got to be real careful about what we make virtual.")

Let me finally say that the arguments of the latter look more convincing to me, but YMMV.

4
  • 2
    I upvoted this, but I think Hejlsberg's distinction between supposed "academic" and "pragmatic" schools of thought is an unfair accusation against academic people in software engineering. There are and have always been very pragmatic academics in this field, long before C# existed, who know that making everything virtual is a terrible idea, for example Bjarne Stroustrup or Alexander Stepanov. In fact, the entire C++ community has always had a rather academic mindset, and yet C++ is an extremely pragmatic language. In fact, I would be surprised if any academic today argued against final. Commented Nov 21, 2019 at 15:30
  • 2
    @ChristianHackl: I don't read this as a rant against "academic people", Heijlsberg probably just wanted to pick a succinct pair of two antagonistic terms for these two schools of thoughts. I would not overinterpret this
    – Doc Brown
    Commented Nov 21, 2019 at 17:55
  • 2
    “any overrideable method becomes part of the API of that framework”—yes, and moreover how the class calls that method becomes part of the API! Does ArrayList’s addAll call add multiple times or directly extend the backing store? It matters if a subclass has overridden add!
    – wchargin
    Commented Nov 21, 2019 at 22:02
  • @ChristianHackl Pragmatic Academics more often than not are people coming from actual jobs in software development, and their work usually reflect this. You'll see a sharp difference between those and "purely academic" - people that have a diploma but very little to no actual experience - in regards to attitude towards code and software in general.
    – T. Sar
    Commented Nov 22, 2019 at 11:16
3

Inheritance is a powerful tool; and like all powerful tools, it can be easy to misuse.  So it's important to consider in each case whether something should be final or not.

It's tempting to think, as the quoted style guide seems to, that by making everything open and extensible you're making things as easy as possible for subclasses, and leaving their options as open as possible.  That seems to make sense (as anyone who's ever tried to extend an overly-locked-down third-party class will know).  But it's not as simple as that…

One of the main problems with making everything open and extensible is known as the fragile base class problem.

The more tightly-coupled two classes are, the more likely that changes to one will break the other.  And a subclass which has full access to all the superclass's internals is very tightly-coupled indeed.  Innocent changes to the superclass can lead to subclasses which still compile, but behave wrongly.  (Particularly if one superclass method is changed to call, or to stop calling, another.)

That's fixable if both classes are under your direct control; but if they're from different modules or libraries, and especially if different organisations are responsible for them, it can be a very serious problem indeed.

That's why Joshua Bloch recommends (in Item 17 from his highly-regarded book Effective Java; Item 19 in the 3rd edition) that you should Design and document for inheritance or else prohibit it.  It goes more deeply into some of these problems, and the actions to take to try to prevent them — such as avoiding calling overrideable methods in constructors, considering carefully which fields and methods to expose, documenting their behaviour carefully including all self-calls, providing judiciously-chosen helper methods for performance reasons, writing multiple subclasses to test the class out, and committing to what you've documented for the life of the class.  He concludes that The best solution to this problem is to prohibit subclassing in ways that are not designed and documented to be safely subclassed.

(That's one of the main reasons why Kotlin reverses Java's policy and makes methods final by default, requiring an open keyword to make them overrideable.)

So it's worth thinking about inheritance carefully when writing a class that could be extended.  You can always make more things open in future; it's much harder going the other way.

-1

There are no cons to the language feature. Whether it should be applied or not depends on the application design.

It is al about filtering, about making things relevant on the application programmer level. A private or final keyword signals there is nothing there for you to be bothered with. In an information-heavy system that can help a lot. It allows you to focus on the stuff that does matter, that helps you create your solution.

So it it not about making your life harder, it is about making it easier. If you would feel the need to mess with a closed part of an object that is a sign you do not understand the tool presented to you. You have the wrong idea about it, you are either trying to apply it the wrong way or it is just not fit to address your problem and you should look further or create something yourself after all.

I like analogies. Suppose you get a box to fit a circuit board in and it has this big red and yellow button on it, obviously designed to serve as an emergency stop. You don't go and use that for something else like a light switch. "These guys are stupid, it is way too heavy and big and I don't like the color which I cannot even change because no paint will stick to this plastic!" Not quite, they helped you along to create a compliant emergency switch. If you don't need one, fine, but do not expect it to be something different.

7
  • 1
    This answer, while I don't disagree with it, implicates nothing in particular asked in the question. It doesn't answer this question specifically.
    – user1936
    Commented Nov 21, 2019 at 17:19
  • @user1936 It is not a direct answer because I consider the question a misunderstanding that I tried to clear up. Commented Nov 21, 2019 at 18:41
  • 1
    Instead of interpreting final as "pay no attention to the man behind the curtain," I think it might be more useful to regard final as signaling "this class is not designed to be inherited." Commented Nov 21, 2019 at 21:58
  • @RobertHarvey: Judicious use of final would to me imply a lot more thought as to how a class should be inherited than would complete omission thereof. The preferred pattern in many cases should be to have a public final method which chains to a protected non-final method.
    – supercat
    Commented Nov 21, 2019 at 22:07
  • I think it's very hard to predict how future programmers might want to use your class, and if you declare public final, even if you spend a lot of time thinking about it, you are just asking for code duplication in the future. Make re-useable parts.
    – user1936
    Commented Nov 26, 2019 at 14:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.