This is one of the features in Kotlin that is enabled by IntelliJ IDEA and the Kotlin language working together like a charm, in order to give you a pleasant developer experience. If you look at the replaceWith documentation, you'd probably move on and never give it a second look. Maybe you'll click through to the documentation of the Deprecated annotation. At least the ReplaceWith docs say what this enables. But because there are no examples, you might just close your browser tab and code on.
But wait! There is so much to love about this. Trust me.
Deprecation of subSequence in Kotlin's stdlib
Let's start with an example. In Kotlin 1.3.72, there still is an extension function String.subSequence
that takes a start
and an end
index. But the Kotlin creators wanted it to be more explicit, so they added a new variant that takes startIndex
and endIndex
as parameters instead. So they deprecated the old one with the @Deprecated
annotation - you can see how they did this here. So if you use the old variant, you'll see it striked through like this:
But because they added a replaceWith
parameter on that deprecation, IntelliJ will offer you to fix this automatically if you hit Alt + Enter
on it - and you can also decide to automatically fix this in your entire project!
Live in action it looks like this:
And all the person deprecating the old variant needed to do was to add this one liner before the old variant, worrying only about the code itself, and by doing this enabling millions of developers to be able to change their code in seconds:
@Deprecated("Use parameters named startIndex and endIndex.", ReplaceWith("subSequence(startIndex = start, endIndex = end)"))
Wouldn't it be nice if you could do the same? Yes, you can!
Replace all the things
Let's use a simple example to start. Say, you have a codebase where someonce decided to put the word do
in front of lots of methods, like doLogout()
. But it's an API, so you can't just get rid of these - you'll have to guide developers to use the new variant logout()
instead. This is how:
@Deprecated(
message = "Use simpler logout() instead",
replaceWith = ReplaceWith("logout()")
)
fun doLogout() {
logout()
}
And automatically, doLogout()
will become for those who use it. And by pressing doLogout()
Alt + Enter
and auto-applying the fix, developers now can update this to logout()
in the entire project.
Ok, but what about this example? Here, we have the parameters in a peculiar order and a peculiar naming, and we want to be able to clean that up, without breaking the project for anyone:
fun doLogin(pwd: String, usr : String) {
login(usr, pwd)
}
Well actually, you can use the names pwd
and usr
in your ReplaceWith string, and IntelliJ will automatically insert the expressions used for pwd
and usr
into the replaced code!
@Deprecated(
message = "Use simpler login instead with correct parameter order",
replaceWith = ReplaceWith("login(usr, pwd)")
)
fun doLogin(pwd: String, usr : String) {
login(usr, pwd)
}
fun login(username : String, password: String) {
}
I didn't see the implementation, but if I would take a good guess this has to work by the IDE taking the old Abstract Syntax Tree (AST), building the new AST of the ReplaceWith
string, and copying over the expressions to the right placeholder positions. If anyone finds where this logic is implemented, please send me the GitHub link - I'd love to include it in this blog post! 🌟
Because it's so nice, here's how this looks if you apply it in the IDE:
It can do even more: add more code, and imports
Let's say we have the doLogin
method from before, but instead of using two parameters, we want to insert a new Credentials
data class that sits in the auth
package:
package auth
data class Credentials(
val username: String,
val password: String
)
This is how we do it:
@Deprecated(
message = "Use simpler login with Credentials",
replaceWith = ReplaceWith(
expression = "login(Credentials(usr, pwd))",
imports = ["auth.Credentials"]
)
)
fun doLogin(pwd: String, usr: String) {
}
When a developer applies the auto-fix with Alt + Enter
, this will automatically add an import as well as fixing the code in-place! This time, let's fix multiple occurrences at the same time, because this looks so cool. Imagine having hundreds of these in your code base, in dozens of files - all fixed automatically! 🎉
Deprecated can do more
That's it for now about ReplaceWith
! One more thing about @Deprecated
annotation: you can add a DeprecationLevel to it as well. The default is WARNING
. But if you want to up the game and you want all developers to finally move over to the new API, you can force them by making this an ERROR
. These have to be fixed first, otherwise their code won't compile. And finally, once all developers have moved over, you can even set this to HIDDEN
- so no one will even see the old method anymore. This is useful if you still want to be binary-compatible. Here is how you can use all these:
@Deprecated(message = "", level = DeprecationLevel.WARNING)
fun warning() {
}
@Deprecated(message = "", level = DeprecationLevel.ERROR)
fun error() {
}
@Deprecated(message = "", level = DeprecationLevel.HIDDEN)
fun hidden() {
}
And here is how they will look when used:
I hope this will be a nice addition to your Kotlin toolbox!
Recap
So to recap, using @Deprecated
with ReplaceWith
lets you:
- deprecate old code, and automatically suggest the desired change
- this desired change will be available to all developers working on that code, and they can apply your code change with
Alt + Enter
instantly - it can even re-order the old parameter values if you need them to, or let you add entire new code constructs and imports
- add deprecation levels to decide how fast users of the old code need to fix this
- be more productive and have more fun!
Edit: Russell Wolf added a comment on Twitter that you can use this feature also for discovering APIs, which I think is really cool:
Have an awesome Kotlin! 🎉
If you liked this post, please give it a ❤️, click the +FOLLOW
button below and follow me on Twitter! This will help me stay motivated to create many more of these posts! 😊
Cover photo by Malcolm Lightbody on Unsplash.
Top comments (2)
Late to the party but the code is around here
Since Java can use Kotlin, how can I make it work for Java too? For example, I have a few helper functions for Java, as this one:
Can Java use the functions from Kotlin, that should be suggested here?