Noisy Code 🗣 With Kotlin Scopes
source link: https://proandroiddev.com/noisy-code-with-kotlin-scopes-331c632739de
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Noisy Code 🗣 With Kotlin Scopes
Scopes make your code more readable? think again
You are going to encounter these scope functions namely let, run, apply, also, with
in every Kotlin codebase, along with all the mischievous ways developers exploit their usage from the way they were intended for. Let see how popular opinion on those ends up just as a code noise.
Featured On
Let me out! ⛓
Popular opinion → let
is treated as a native way to do null checks on the variables.
Please don’t do that because using let feels functionally beautiful but
- You will end up creating another reference called
it
in the same scope which is less descriptive - To fix that you will definitely try to provide an explicit parameter for lambda example
imageFile?.let{ image →
, - but it creates a load of find another name for the same variable (naming things are hard)
- Now other devs can refer to image files in the scope with two names
imageFile
andimage
, with time you need to maintain that the right variable is being used inside the scope of lambda when there is iteration over the code for bugs and changes.
We can avoid these and other hiccups just by doing normal null checks.
And for guys who are too smart will argue that smart casting won’t work on global fields. I will say read my article 👉 [lateinit vs nullable types | global fields] or watch my video 👇
Video is in HindiAnd stop polluting your global space.
The reason why smart cast fails because the global field can be accessed by other functions of the same class. In any async situation, global value can update at any time so it’s impossible to smart cast.
So, to fix that you can use
Since you will end up making a new variable anyways which might be of a different name, you can get away with using let
in such cases.
PS: don’t forget to make it
descriptive
Another Bonus part …
- Nesting scoped function is considered bad practice even Kotlin style guide point this out.
- Prefer using
if
for null checks then you can use scope operators where it is actually required.
Community Feedback
Thanks,
Provides better alternates regarding the use of let, I definitely loved all the approaches.// alternate 1
fun deleteImage() {
val imageFile = getImage() ?: return
...
}---- OR -----
// alternate 2fun deleteImage() {
getImage()?.takeIf { it.exists }?.let {it.delete()}
}---- OR -----
// alternate 3fun deleteImage() {
val image = getImage()?.takeIf { it.exists } ?: return
image.delete()
}
In this way avoided assigning a new value to a variable in the function scope, so It do not have the double name problem.
Also, Apply my rule! 👑
Edit 1: This section has been updated after the feedback from
.Popular opinion → Create scope to isolate common functionalities to modularize your code.
The pure functions|lambdas generally are expected to be one-liner expression. This has been explained really well by Sir Venkat Subramaniam in one of his talks about functional programming, highly recommended to consume all of his work!
- Lambdas shine out in making code concise by still retaining the expressive parts of it.
- Generally, lambdas with huge function bodies end up hindering the readability of the functionally chained pipeline you want to create.
So chaining with small lambdas doesn't disrupt the readability of your code.
From the above example
- The scope of
intent
is limited insidealso
, theintent
is restricted to that lambda itself - This will make you add all the code that needs to access
intent
into the same scope - It will not take a lot of time when
also
lambda becomes a huge mess
A simple fix to it … Flatten your hierarchy when possible
Flattened and not scary anymore, there was no point in making another scope introduce another scope for just consume the object.
Community Feedback
Thanks,
for your insights. Your example on abstracting out into thefunction
is really clean, definitely works best to control chaining.fun startSomeActivity() {
startActivity(getSomeIntent())
}fun getSomeIntent() = Intent(context,SomeActivity::class.java).apply {
//....
}
Run from Run ?: 🏃🏻♂️💨
There is two run
in Kotlin one is a scope/extension function and the other is a normal higher-order function.
popular opinion → It’s commonly used to execute some statement.
Wtf is this?… absolute disappointment I suppose.
- Absolutely yes you can chain scoped operators like this but should we do it? NO!
- Not many devs are aware that there are two types of
run
in the language and having both of them chained using Elvis operator is a complete disaster. - It adds no value to the code, just branching and noise
And smart devs who are thinking that this pattern might be useful when you have more line of code there instead of the one-liner, go and read Apply my rule section again! else it’s a democratic country do whatever you want.
Community Feedback
advice to come up with some helper extension functions you can use throughout the project, or use Kotlin native one.preferenceManager.getInstance()?.getName().orDefault {
Log.w(“Name not found, returning default”)
getString(R.string.stranger)
}
Which really is a good way to architect your code, here the point of focus is to apply a pragmatic approach instead of chaining scopes mindlessly. Use your tools wisely.
You are not With me 😭
The most ignored one, and not an extension function.
Devs often ignore with()
for two reasons
- It takes receiver in parameter, not in extension, so people can’t do function chaining with using it.
- Secondly, doesn’t go well with nullable types receiver
That’s really sad, with is a really useful scope operator. IMO, it has very special a use-case, cause it's not good for receiving parameter and make function chains but it is very useful when you want to set the scope of the variable into the entire function body, see this example
If you compare it with other scoped operator example :
Anyway this ugly is an example of how to cater to nullable types as well, you can use run with nullable here, but I will highly suggest not to pass nullable parameters into your functions.
Community Feedback
advice to useapply
, as it’s not returning the run
result. That would work better than run
absolutely agreed.Conclusion 💆🏻♀️
In isolation using these extensions does look sensible and pragmatic but as whole they are not readable or maintainable, we lose the intent of what we wanted to modularize and so many blocks make it really hard especially in code reviews, always leverage Kotlin features where they do good.
Hope you find it informative and if you have any feedback
or post request
or want to get in touch, I'm always available on my social media all links below
If you liked my content and want to read more check out my awesome blogs!
Check what I’m upto
I’m an active member of an OpenSource Organisation called theCodeMonks, do check out the impact and knowledge we are sharing with communities
Do consider
Clap
to show your appreciation, until next time. Happy Hacking! 👩💻
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK