Skip to content

overridden_fields documentation is vague #58311

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

Open
jamesderlin opened this issue Jan 23, 2021 · 6 comments
Open

overridden_fields documentation is vague #58311

jamesderlin opened this issue Jan 23, 2021 · 6 comments
Labels
area-devexp For issues related to the analysis server, IDE support, linter, `dart fix`, and diagnostic messages. contributions-welcome Contributions welcome to help resolve this (the resolution is expected to be clear from the issue) devexp-linter Issues with the analyzer's support for the linter package linter-set-recommended P3 A lower priority bug or feature request type-documentation A request to add or improve documentation type-enhancement A request for a change that isn't a bug

Comments

@jamesderlin
Copy link
Contributor

https://dart-lang.github.io/linter/lints/overridden_fields.html states:

Overriding fields is almost always done unintentionally.

If @override is used, it quite clearly is not unintentional.

Regardless, it is a bad practice to do so.

Why is it a bad practice to do so? It would be nice to provide examples. #57764 provides some arguments. However, I don't fully buy some of them.

  • From the perspective of an API consumer, including the derived class, fields are indistinguishable from a getter and setter.
  • Getters and setters are functions.
  • Overriding functions is okay.

Are we also saying that overriding get and set functions from a base class also is bad? What about overriding a base class field with get and set functions?

@pq pq added the type-documentation A request to add or improve documentation label Jan 23, 2021
@pq
Copy link
Member

pq commented Jan 23, 2021

@pq pq added the contributions-welcome Contributions welcome to help resolve this (the resolution is expected to be clear from the issue) label Jan 23, 2021
@lrhn
Copy link
Member

lrhn commented Jan 28, 2021

Overriding a field with another field is rarely the thing you want to do because it means the object has two storage locations for what is effectively one property.
I'm sure I can come up with a situation where it makes a kind of sense, but it's going to be highly speculative.
If you further make the super-class field completely unreachable by not providing any super.name access to the original, then the original is definitely unusable, which is unlikely to be a good deliberate design. You'd likely be better off implementing the interface than extending the superclass.

Overriding a field with a getter or setter which calls the super-getter/setter and then does something more, that's completely reasonable.

So is overriding a getter/setter pair with another getter/setter, and unless the subclass is in the same library as the superclass, it shouldn't even know whether the superclass uses a field with implicit getter/setter or an explicit getter/setter declaration.

Whether something is a fields is an implementation details, it should be able to change from version to version of a package without anyone noticing. Blindly warning about overriding a field, but not a getter, breaks this abstraction.

(If it's possible to detect that a field is overridden and unreachable, and it's declared in the same library, then there might be a good reason to give a warning, because you're in position to actually do something about it. Anything other than that is risking false positives.)

@moneer-muntazah
Copy link

Thank you both for the post. @lrhn I came across this discussion after coming up with this idea, and getting the lint rule.

class Product {
  Product({required this.sku});
  final String sku;
}

class CustomProduct extends Product {
  CustomProduct({required Product product, required String customSku}) : sku = 
    customSku, super(sku: product.sku);
  
  @override
  final String sku;
  
  Product get parent => Product(sku: super.sku);
}

The real example is more complicated and has more fields, and methods, but the idea is that my CustomProduct will sometimes need to give back the original Product, but needs to be treated by the pluming of my app as a Product. Would you recommend overriding fields in this instance? And if not why. I realize that the idea looks a bit convoluted which is why I'm hesitant to implement it, but I don't want to change the pluming so it's very convenient for me to subclass.

Thank yo again

@lrhn
Copy link
Member

lrhn commented Aug 17, 2021

I'd probably prefer composition over inheritance, so:

class CustomProduct extends Product {
  final Product parent;
  CustomProduct({required Product product, required String customSku})
      : parent = product,
        super(sku: customSku);
}

This way there is no overriding the sku field, and everything except the parent getter is a completely plain Product.

Alternatively, if you really want to store the parent SKU instead of the parent object, I'd do:

class CustomProduct extends Product {
  final String _parentSku;
  CustomProduct({required Product product, required String customSku})
      : _parentSku = product.sku,
        super(sku: customSku);
  Product get parent => Product(sku: _parentSku);
}

Either is easier to read than the unnecessarily overriden sku field.

You generally never need to override a field with another field. Overriding with a getter/setter with side effects makes sense. Overriding a final field with a mutable field makes a kind of sense, but feels icky, I'd probably prefer reimplementing the interface instead.
Only ever do it for classes where you also wrote the superclass, because otherwise you can't be sure the superclass doesn't do super.field in some methods and this.field in other methods to get to the field. Your override won't affect super.field. In those cases, I'd make a skeleton base class instead, where the field getter is abstract, and provide different subclasses of that instead.

@srawlins srawlins added the P3 A lower priority bug or feature request label Sep 25, 2022
@filiph
Copy link
Contributor

filiph commented Apr 10, 2023

Here's something I tried to do at least a couple of times:

class A {
  String field = "A generic default initial value for field.";
}

class B extends A {
  @override
  String field = "An initial value that makes more sense for the subclass.";
}

For what it's worth, I didn't care about A.field becoming unreachable. I didn't even realize it's still there! I just thought I'm overriding the initialization.

To avoid the lint, which I never fully understood until now, I learned to do the following:

class A {
  late String field = fieldDefault;
  
  String get fieldDefault => "A generic default initial value for field.";
}

class B extends A {
  @override
  String get fieldDefault => "An initial value that makes more sense for the subclass.";
}

(I realize I could do it in the constructor without introducing a new getter, but that can make the classes a bit harder to maintain, imho.)

Anyway, I just thought I'd add this point. Maybe I'm the only one who wrongly assumed that overriding a field like this is just giving it a different initial value in the subclass. If not, does it make sense to mention this misconception in the lint warning?

@bwilkerson
Copy link
Member

Yes, it makes sense to mention it. Thanks for pointing this out.

@srawlins srawlins added the type-enhancement A request for a change that isn't a bug label Mar 29, 2024
@devoncarew devoncarew added devexp-linter Issues with the analyzer's support for the linter package legacy-area-analyzer Use area-devexp instead. labels Nov 19, 2024
@devoncarew devoncarew transferred this issue from dart-archive/linter Nov 19, 2024
@bwilkerson bwilkerson added area-devexp For issues related to the analysis server, IDE support, linter, `dart fix`, and diagnostic messages. and removed legacy-area-analyzer Use area-devexp instead. labels Feb 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-devexp For issues related to the analysis server, IDE support, linter, `dart fix`, and diagnostic messages. contributions-welcome Contributions welcome to help resolve this (the resolution is expected to be clear from the issue) devexp-linter Issues with the analyzer's support for the linter package linter-set-recommended P3 A lower priority bug or feature request type-documentation A request to add or improve documentation type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

8 participants