Nothing wrong with !! IMO I think both <@U2E974EL...
# android
c
Nothing wrong with !! IMO I think both @elizarov and @Adam Powell have both mentioned on here a few times that !! is a great way to assert an invariant. If they are using !! to simply satisfy the compiler, then yes that's bad. But if they are using it to say "this should never be null" then its fine to use and more convenient than other methods. Of course there are exceptions, but most people don't like !! because they think it looks ugly or screms that something is wrong. Which I found myself doing in the early days of kotlin, but now I use !! everywhere. πŸ˜„ tagged both of those users above because I don't actually want to misconstrue their opinions, but I think in general they both think !! is fine
a
If it should never be null then you're not dealing with nullable types in the first place to need !! πŸ™‚ It's not bad to use it here and there, but if it's truly everywhere there might be some better ways. Local vals that you don't have to recheck,
.let {}
, etc.
βž• 3
in a 25kloc kotlin hobby project that spans both an android app with compose and a number of backend binaries, I have exactly 36 instances of
!!
g
Is it in production only code?
200k lines of Kotlin in our project, 69 cases in production, 132 cases in tests
a
I mean, "production" in that it powers my home automation, runs for months at a time without attention and doesn't get me yelled at my my family for the light switches not working πŸ˜… it was the quickest project I could run the check on
g
I mean not in tests
a
oh, yeah not tests at all
g
No tests, no double bang there, no problem πŸ‘
πŸ˜‚ 2
a
probably about 25-30% of the instances are because I use it to ensure that type inference gives me a non-null val rather than a platform type when calling other java code/libraries
for libraries and such without nullability annotations but that won't return null
c
@Adam Powell well lets say I have a field that can be nullable and in fragment A I treat it as such, but I use that same field in fragment B and by fragment B I know that it should not be null (as per some business requirement or something). That would be a valid use case no?
a
Sure, I might try to find a way to pass it forward such that it doesn't need the
!!
, but it's not the end of the world
The use of, "everywhere" is a little disconcerting πŸ™‚
c
Yeah. I guess my point is still that !! isn't there to satisfy the compiler. It's valid to use. The only place where I strictly stay away from it is when you have a single line and if you start chaining !! then it becomes hard to reason about. In that case then I use other ways to assert.
m
by fragment B I know that it should not be null (as per some business requirement or something). That would be a valid use case no?
FWIW, I would argue "no". The problem is that "I know that it should not be null" is a short-lived state. Code changes. Platforms change. Devices change. Developers change. Business requirements change. And one day,
null
might show up as a result, and then your app crashes hard.
!!
is convenient but unforgiving.
πŸ‘ 3
c
@Mark Murphy what would you recommend there though? I feel like if you just use a
?
you're just potentially hiding issues. I would rather take the crash over unexpected behavior in the app? Unless you're still saying that I should assert the invariant, but I should instead use some other method.
m
I feel like if you just use a
?
you're just potentially hiding issues
You handle the possibility of
null
. Exactly what your response is to
null
is would depend on your business logic. If nothing else, log a message with whatever sort of system you're using for that sort of thing (e.g., log a non-fatal with Crashlytics) and deal with the situation gracefully for your user.
I would rather take the crash over unexpected behavior in the app
Your users might not like that approach. If you feel that you have a sufficiently-robust test suite that 100% of
!!
crashes would be caught in testing, so you feel comfortable with
!!
, you're welcome to go that route. I don't know of many places that have testing strong enough that would make that approach viable, but that's just me.
c
Thanks for the perspective @Mark Murphy and @Adam Powell and @gildor! Lots to think about!
a
if some part of your code requires something to be non-null, it's generally good to try to structure that such that you're passing a non-nullable parameter to a function that performs the work or similar. Extrapolate that on up through your stack like squeezing a tube of toothpaste and you end up with very few places to use
!!
😬 1
☝️ 2
in a way it's a big perspective shift at the design level. You build fewer state machine classes where a temporary/uninitialized null hangs around in a property
c
Yeah. I mean that's mostly where all of this comes from. Technically a lot of my values are "null" until it becomes initialized, and then it should be there for the later parts of the flow. If it's not... that's where I want things to crash because something is wrong in the universe.
a
yep. Try to see if you can avoid creating the objects that hold that data at all until you have all of the data present to construct it with the valid state
☝️ 1
πŸ‘ 1
it's definitely not an overnight shift in thinking
πŸ’― 1
and some other tools like suspend functions help a lot here. A suspend function is a factory for state machines, and since you can declare local vals midway through a function once you have enough data to fully initialize them, you have some very clean ways of expressing this idea. Consider the difference between
Copy code
var result: Result? = null
doFirstThing()
result = doSecondThing()
vs.
Copy code
doFirstThing()
val result = doSecondThing()
in the second version result never has to be declared as nullable
πŸ‘ 1