If you have a method calling another public method, you must consider that as part of your public API. You can not refactor this to use a private method instead, or change how it calls the public method, because you don't know if someone else is going to try and override your public method.
Not strictly related to the fragile base problem, but, because your public methods may not be designed to be overridden, if an end-user overrides those methods, it could result in subtle bugs happening. The end-user may not even be intentionally overriding a method, they could be trying to add a new method, but they accidentally choose a bad name.
It turns out that inheritance isn't the only way to run into the maintainability issues associated with the fragile base problem. You can also bump into it by, for example, having a consumer of your class calling your methods via
.call(), and using their own special "this" values, or, by mutating your class. In either of these cases, they would be able to construct code that's capable of depending on how your methods call other public class methods. I'm not overly worried about the class-mutation issue, since that can trivially be solved today with a couple of lines of code by freezing the prototype and preventing extension on the instance, though, it could be nice to find a way to incorporate it in. The issue about
.call() is still important - it would be nice to find a way to handle that as well.
(The above is very high-level, if any examples are needed to help explain these issues, I could put some together).
So, to summarize, I'd like a not-overly-verbose way to discourage or prevent specific behavior(s), like inheritance, on my class, such that, as a maintainer of that class, I don't have to worry about the fragile base problem.
I'd like to propose the addition of the "final" keyword, which we can stick before the "class" keyword to make it a "final" class. I'm choosing the term "final", because it achieves a similar goal as Java's "final", however, this doesn't mean that the keyword's behavior needs to be the same. If we must, we can always switch to using a different, made-up keyword instead if we think "final" would be too confusing.
As for how we need to go about doing the actual implementation of "final", I see the issue around
.call() on methods as the root issue. If we can fix this, then the inheritance issue sort-of clicks into place. We can fix the
.call() issue, by simply having all methods in a final class automatically do a bit of verification whenever they get called, to make sure the "this" parameter they're receiving is a direct instance of the class (direct == not down an inheritance chain), i.e. there's an assertion that makes sure that
Object.getPrototypeOf(this) === TheFinalClass.prototype.
If that restriction, alone, is in place, then it's technically useless for someone to try and inherit from a final class. They could, but if they tried to use any of the inherited methods, they'd just get an error. That being said, it could still be nice to add some other, additional features - none of these are strictly required to solve the original problem statement, but if we can pull them off, it does make the "final" keyword more user-friendly.
It's a runtime error if you try to use the class syntax to extend a final class
It's a runtime error if you try to ever put a final class's prototype somewhere in a prototype chain. This is a fundamental change to how prototypes work, but right now, I can't think of any particular reason why this change would be bad.
We could auto-freeze the prototype, and auto-prevent-extension on the instance. The prevent extension action could either happen right after the constructor runs, or we could require you to declare all needed fields via public fields, and the prevent extension happens before-hand. I think it could be appropriate to make the "final" keyword do this sort of task in addition to preventing inheritance - the idea behind "final" is that the class is in its "final" state, you can't go modifying it, or overriding behaviors, or anything.