I didn't find a KEEP for extension functions. I'm ...
# getting-started
g
I didn't find a KEEP for extension functions. I'm looking for good guidance on when to/not to use extension functions. The code I'm reading is littered with them, and some of them really smell, e.g. an extension function on Collection<PairString, String>. Any suggestions?
k
There's a little bit of guidance at https://kotlinlang.org/docs/coding-conventions.html#extension-functions though I suspect you're looking for something more extensive and detailed.
g
Oh I missed that actually
😢 > Use extension functions liberally.
Welp. I lose this round, Kotlin.
c
well, yes, use the liberally where they add value - but other overarching idioms also apply, such as limiting the visibility to the minimum necessary. For localized convenience extension functions I’ll start with those being private inside a class, then perhaps file-private, then internal, though I would start to create more meaningful types than
Collection<Pair<String, String>>
well before that for a bunch of reasons.
r
@elizarov wrote an article on it a few years back that's still worth a read: https://elizarov.medium.com/extension-oriented-design-13f4f27deaee
c
The rule of thumb I follow is basically: "is this operation really done by the receiver?" If so, then it's ok as an extension function, but weird ones are only allowed to be private or file-private.
y
I think your issue is not extension functions, it's more fundemental than that. You're not modelling your types right. A
Collection<Pair<String, String>>
is definitely a code smell unless it shows up somewhere in an intermediate calculation. Instead, use types liberally. Create data classes, value classes, interfaces, etc to model your domain correctly, and then use extension functions to model things that aren't core to those types but might be useful utilities for them
c
Another rule: does it always make sense for the receiver type? I once saw a
String.toDatabase(): Database
. This makes no sense, because you cannot convert a string into a database. What the code did was create: • create a database connection • assume the string is a connection URL • use it as settings To me, this is way too many assumptions. However,
String.asEmail()
makes sense, because any string could indeed be an email.
y
I would argue that
String.toDatabase
, while obviously bad, can instead be broken down to
String.toUrl().toDatabaseConnectionSettings().connect(): Database
or something along those lines, where the intermediate types help model the steps that happen and thus allow you to configure things more granularly.
c
^ I agree. It also pollutes auto-complete much less, because you only ever see things that are actually relevant to the type you are handling.
g
Thanks for the thoughts, pals. A lot to consider here. The team that I’m working with is relatively new to Kotlin and has been largely learning by reading Kotlin that another team wrote. (They inherited a rather large Kotlin codebase.) I just joined the team and am trying to figure out how to write more idiomatic Kotlin, because some of what I’ve seen in PRs is questionable, like this Collection<Pair<String, String>> bit which is used exactly once and could easily have been a call to map.