-
-
Notifications
You must be signed in to change notification settings - Fork 345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Now that we have a mechanism for blocking subclassing, where should we use it? #1044
Comments
From a quick skim, here are some classes that should probably be marked
For |
This is some kind of argument, but I'm not sure if it's for or against :-) https://trio.discourse.group/t/socketstream-putback/208/8?u=njs |
|
@belm0 yeah I thought about bringing this up in #1199, but decided there wasn't any point. |
I think this issue has a pretty compelling argument that we should be marking most of our classes as final: #1247 Specifically, notice the subclass of So, we're forced to pick one:
None of these is ideal. But given that we have to pick one, I think (3) is the least-worst. |
Another option would be (4) refactor the code of affected classes so that no public method calls another public method. Yes I know you're generally not in favor of the "add behavior by subclassing" pattern, and there are reasons for that, but our users might still have their own reasons for doing things "their way". Trio shouldn't take options away from its users without a compelling reason. |
AnyIO currently subclasses |
"We don't want to have to carefully audit and refactor all our code" is a pretty compelling reason IMO :-). We'd need to like... implement some kind of static analysis to enforce that public methods never call public methods? That sounds like a lot of work for unclear advantages. (We can also make things final now, and change our minds later on a case-by-case basis – if we decide that it's worth doing the work to properly support subclassing for some class, then transitioning from final classes → allowing subclassing is a backwards compatible change. So we can afford to hold off on investing in static analysis etc. until we find a strong motivating example.) |
#1501 followed through with making most public classes final. |
Followup to #1021
Possibly we should use it on... all classes? (Except ABCs.) Possibly not.
Edit on 2020-05-07 to add more rationale: The question here is about whether we should forbid subclassing on Trio classes.
Subclassing is a controversial topic in general – some people argue that it's an anti-pattern in general; some think there are situations where it's appropriate. But I think even subclassing advocates mostly agree that subclassing only works well when the base class is intentionally designed to be subclassed. In particular, without this, you can easily create fragile coupling between the internals of the base class and the subclass. For example, let's say we have a base class that uses the common trick where one method is implemented using another:
If you make a subclass and want to customize how all the different
my_op
variations work, the obvious probably just overridemy_op_basic
, because that automatically covers both cases:We test it, and it works great: our custom logic runs on
SubClass.my_op_basic
andSubClass.my_op_extended
, becauseBaseClass.my_op_extended
internally invokesSubClass.my_op_basic
. But then, someone refactorsBaseClass
, and moves the main logic intomy_op_extended
:Now,
BaseClass
's public API hasn't changed at all – but this internal refactoring is still a breaking change forSubClass
! Now calls toSubClass.my_op_basic
will invoke the custom logic, but calls toSubClass.my_op_extended
won't!The point is: subclasses don't just rely on base class public APIs; they also rely on details of how the base class methods are implemented. And then something that looks like an innocent refactoring suddenly becomes a breaking change. That can be fine in some cases: if both classes are in the same project, then the tests will probably catch it and you can refactor
SubClass
as well. Or, if the base class was explicitly designed for subclassing so these internal implementation details are documented as part of the public API, then it's not an issue.But for most of the classes that Trio exports as part of its public API, the potential subclassers are Trio's users, and we can't see their code. So if we do an innocent-looking refactoring and it breaks our users code, we have no way to notice that until we ship and their system falls over. And, most of the classes we export aren't carefully designed with subclassing in mind – that takes a lot of work, and isn't useful in most cases. (The main exception are the classes in
trio.abc
, which are explicitly designed with subclassing in mind, and their inter-method dependencies are explicitly documented.)We make promises to our users about API stability. For most of our classes, if users are subclassing them, those promises are currently a lie – we actually have no idea which of our changes might break user code. Lying is bad. It's better to explicitly disable subclassing and give users a clear error up front, instead of letting them ship code that relies on subclassing and then starts failing in mysterious ways.
The text was updated successfully, but these errors were encountered: