Hi! Are there any recommendations on how to use ex...
# announcements
m
Hi! Are there any recommendations on how to use exceptions in kotlin ? With the lack of checked exceptions, we've been hit a couple of times by missing
catch
clauses. For an example IOExceptions on files/connections that made the app crash while we should have just shown an error screen. We were considering removing exceptions altogether from our codebase and just rely on return values. Legacy java dependencies like
File
would still need
catch
blocks and that's okay. But then we realized that even new libs like retrofit-coroutines throw exceptions. How are you handling this ? Do you have a lot of exception handling in your codebases or do you use return values ? Is it okay for kotlin libs to throw non-fatal exceptions ?
t
We make heavy use of reactive-streams (even in a standard BE RESTful service) and ensure that all error propagation is handled via that. So we end up with pretty much no try/catch blocks anywhere and (mostly) don’t have to worry about it.
m
reactive-streams you mean RxJava ?
What I don't like about the RxJava
onError
handling is that it eats fatal programming mistakes silently. Fatal and non-fatal exceptions are handled the same way.
t
No. We use Spring Reactor, but conform to a standard
Publisher
interface where possible
What do you mean it eats mistakes silently?
m
a fatal programming mistake (taking an element outside the bounds of an array for an exemple) will end up in
onError
the same way as a non-fatal recoverable one (a HTTP error)
t
Ok… but as the programmer that’s up to you to handle. I don’t see how that’s different no matter what tech stack you choose
If we encounter recoverable errors that we want to either retry or provide a default for, we handle them. Everything else bubbles up.
m
this means in your
onError
, you need to explicitely check the type of the exception (sorry for the RxJava reference, I don't know much about spring reactor). And knowing in advance what kind of exception might be encoutered is hard as the chain becomes longuer
It's either 1.
Copy code
longChainOfObservable.subscribe({}, {displayErrorScreen()})
or 2.
Copy code
longChainOfObservable.subscribe({}, {e->
        when (e) {
            is HttpException -> displayErrorScreen()
            else throw Exception("unhandled exception in stream", e)
        }
})
1. ignores programming errors 2. it's too easy to forget other types of exceptions like
SSLSocketException
or
FileNotFound
, ...
t
That’s why you keep the error handing close to where it would happen (which is no different to exception based too). The code responsible for handing the http comms will do whatever it needs to with local errors and map them to a common one (or retry, return a default etc.). The outside doesn’t worry about it
💯 2
So the HTTP code can in fact do something like:
Copy code
httpObservable.doOnError(.... things to handle errors)
, which will catch ALL errors, before returning to the top.
m
so you don't need a
onError
handler in the end, right ? Because all exceptions would have been handled earlier in the chain ?
t
So
longChainOfObservable
could never see
HttpException
, it would see some domain exception I invent
m
Ok so you map errors
t
You do - but again that’s no different to standard exception handling. That one is there for to handle things you didn’t think of
m
Now back to the original question, the thing with standard exception handling is that java checked exceptions are present in the method signature. So you know calling
lib.doCall()
can throw a
IOException
. With kotlin this is not true anymore. If
lib.doCall()
throws and you forget to add handling code, your app crashes 😕
And you have no clue without reading the implementation that
lib.doCall()
can actually throw
s
why not just use coroutines and have a handler in your supervisorscope that can swallow/retry/whatever or rethrow if it isnt a whitelist of exceptions you're happy with. imo the forgetten IOException where it failed fast is what you want, you then go in and fix it (if it wasnt picked up in integration testing)
t
So the same as I was saying for reactive code - handle all exceptions (not `Error`s) at a point you think you can recover from. Ignore checked vs unchecked - they’re all equal on the JVM
m
@sitepodmatt The thing is: how do I build the whitelist considering kotlin does not expose the exception in method signatures ?
s
checked exceptions were always overrated anyway because people just handled Exception/Throwable or for example IOException - some of derived types you dont want to retry
m
I didn't 😉
s
function somethingJava() throws IOException doesn't really help
t
In Java land we used lomboks
@SneakyThrows
on most things and treated checked as unchecked exceptions
m
@sitepodmatt it means you need a
catch
and if you omit it your app crashes. That's valuable information.
t
But that’s not a definitive list of what it might throw, just the things it declares it throws. Any other kind of
RuntimeException
could still be thrown
m
Yes, • RuntimeException (unchecked exception) = fatal = your app crashes • Checked exception = non-fatal = you can recover
If a lib throwed an unchecked exception to signal a recoverable error then it's a bad bad lib 🙂
s
RuntimeException doesnt mean you can logically recover (Im think at the business level)
m
yea I'm confused now 🙂
t
Your definition is confusing -
RuntimeException
== unchecked exception
The only definition that really counts is that a
RuntimeException
doesn’t need to be explicitly handled by the caller, a checked exception does.
Which is why I’m suggesting that you treat them all equally in terms of handling. The only difference is at the Java language level. Within the JVM they’re treated the same
m
yep, sorry
I edited my message
t
Your users don’t care that a lib you used didn’t conform to a guideline!
m
As a developer, I do
t
Ok, but you have the idea that a runtime exception must be fatal - that’s just not true.
m
true that it's not true 🙂
But this is exactly what I'd like to understand. It would be nice to have a kotlin standard for this
So we know what to expect
In kotlin, using exceptions for recoverable error handling seems dangerous to me.
But maybe it's just me.
Just to get to the bottom of this: do you have an example of a lib/method where a RuntimeException is actually not fatal ?
t
com.amazonaws.SdkClientException
. These actually have a method on them to let you know if it’s a retryable (recoverable) error or not
m
Thanks
For now, I'll go with: 1. when calling dependencies, handle the exception as close as possible to the callsite. 2. hope that library maintainers document the thrown exceptions as clearly as possible. <= this is where I believe we could have some better guidelines 3. for library code that I write, don't expose exceptions in the API (okay to do it in the implementation I guess). 4. for app code that I write, use return values for everything that can be recovered. throw exceptions to exit the app.
t
I honestly think a lot of it doesn’t matter as long as you don’t violate the principle of least astonishment, and you’re consistent
m
I just like strong typing and not having exceptions in the api contracts still bugs me. Ultimately, if we're all handling exceptions close to the callsite then they're not much different from return values.
t
I think you’ll find yourself at odds with a huge portion of the Java world, and I would say all the kotlin world if you want checked exceptions!
m
https://en.wikipedia.org/wiki/Exception_handling#Criticism
Copy code
Go was initially released with exception handling explicitly omitted, with the developers arguing that it obfuscated control flow.[16] Later, the exception-like panic/recover mechanism was added to the language, which the Go authors advise using only for unrecoverable errors that should halt the entire process.
Looks like I should switch to golang 🤔