dart-lang / site-www

Source for Dart website
https://dart.dev
Other
968 stars 700 forks source link

[FEATURE]: 'Effective Dart: Design' guidance for using extension methods #4409

Open dcharkes opened 1 year ago

dcharkes commented 1 year ago

Page URL

https://dart.dev/guides/language/effective-dart/design

Page source

https://github.com/dart-lang/site-www/tree/main/src/_guides/language/effective-dart/design.md

Describe the problem

We could add guidance on when to use extension methods and when not.

Expected fix

Some ideas:

When choosing between a top-level function or an extension method:

When choosing between a wrapper class or an extension method on generic classes (e.g. class FooList extends List<Foo> vs extension FooList extends List<Foo>:

cc @lrhn @mkustermann

lrhn commented 1 year ago

I'd expect a lot of people to have opinions. I also expect it to be something where it's hard to make clear and actionable recommendations.

I've seen the opinion that static/top-level helper methods are just a thing of the past, and you should always use extension methods. I won't go that far. There is something to it, though.

Extension methods have better discoverability in an IDE, worse when reading documentation. Neither works if not imported. Searching for an extension method can be harder, because its name usually omits mentioning the on type, e.g., it would be named sort rather than sortList. That's all ... inconclusive.

For the ideas here, the "make specialized function for each accepted type, instead of accepting a supertype and throwing on some inputs" is a good advice, but applies equally to extension and top-level functions.

Fluent APIs are almost always better, which means that you really should only use extension methods for anything you expect people to use often. Or even close to often. It also allows ?. and .. invocations, which is more flexible than a top-level function. Extensions win, hands down, in usability.

Generally, you should probably use extensions unless you have a darned good reason not to.

That reason can be that it's not clear that one of the arguments is more important than the other. if you want to check whether a string is the canonical textual representation of an integer, aka. n.toString() == s, it's not at all obvious that that's a general method that belongs on either String or int.

It's a weird and oddly-specific method on String. It also feels a little weird on int. The name would be isStringCanonicalRepresentation or something? By itself it would be

bool isCanonicalRepresentationString(int number, String representation) { ... }

That actually suggests the rule underlying my unease here: Don't expose functions that are too specific. Which I guess is what the containsAboveThreshold is about. If the function is very domain specific, don't make it a public extension method. (You can still make it private to your own code, nobody cares what you do internally.)

Adding extension methods to a type does pollute a common namespace. Do so only when it's worth it. Otherwise make sure that users have to opt-in to the extensions, and know that they have to. (I'm not sure whether int List<int>.sum() would make that cut.)