FYI this is a good reason not to use exceptions as...
# coroutines
z
FYI this is a good reason not to use exceptions as a way to control flow. Use sealed classes
l
I don't agree, exceptions are good for implementation. sealed classes too, but should not replace exceptions. Said otherwise, it depends.
IOException
should not be replaced by a sealed class everywhere (if anywhere at all) for example
z
why wouldn’t you just wrap the result in a typed object for it. Something like `Result<T>`…similar to
Try<T>
in Scala/Arrow?
l
Because I do multiple things that may fail, and I want to deal with the types that come in the successful case. Only at the end of the chain it may make sense to wrap it in a custom sealed class to expose to UI code for example.
z
then your downstream logic is affecting how you design your upstream logic
you can just unwrap the
Result<T>
if you want that behavior downstream
testing that code also becomes much easier to reason about
l
I'm not sure what upstream or downstream means there as I don't use this naming, but basically, I'd make the code with raw types that can throw have a try catch, and make a result from the success try branch or from the catch branch(es). Note that I use coroutines a lot, so I wrap into a result or sealed class only at the end if relevant, but for all other suspension points that can fail, I use raw types and catch to make a result
Then, the code is both readable and easy to reason about
z
Upstream would be your suspending function that throws or returns a
Result<T>
. Downstream would be your Ui or ViewModel or something that calls that function. It may be obvious to you, but it’s not going to be obvious to other developers. It’s why
String?
in Kotlin is better than
@Nullable String
in Java. One is enforced by the compiler, and one isn’t. If you’re nesting multiple function calls that each return
Result<T>
, then most implementations of `Result<T>`/`Try<T>`/etc have the ability to flatten
Result<Result<T>>
into a single
Result<T>
. The reason I mentioned testing is that mocking/faking a method to return a
Result.Success<T>
or a
Result.Failure(exception)
is easier than telling a method to return a value or throw
a
As a FP-addicted guy, i fully get the point Zak is saying [there is nothing inherently bad with exceptions per-se, it's when it comes to use them as a control-flow that various drawbacks occur.. also it means the contract of an interface is somehow lying because it's not clear from the signature alone of a function on the interface which exceptions can be thrown and you have to inspect the source code, or alternatively be hyper-defensive and be prepared potentially to deal with all sort of exceptions 😄 ) HOWEVER, this has nothing to do with suspend functions themselves: the same architectural discuss can be done for ordinary functions. Even more important: if you have a coroutinescope with multiple async running in parallel (ie, parallel decomposition, the idiomatic usecase for async-await, otherwise just use withContext) and exceptions are not used to notify errors, the benefits of early release of resources / children cancellation (after structured concurrency was introduced) is gone and you should take care about it manually.
👍 2