I am using Guide to app architecture as a reference for my development. Thank you!
One issue that often comes up in my development team is whether to throw Exceptions or use Results when designing with suspend functions and Flows.
I think there are advantages and disadvantages to both.1. The advantage of using Exception is that you don't forget to convert it to Result. You can use a coordinated error mechanism in Kotlin coroutines.
suspend fun fetch(): List<Item>
2. The advantage of using Result is that you can specify the type of error and ensure that the API user can handle it.
suspend fun fetch(): Result<List<Item>, FetchError>
In the architecture guide, there is a statement that you should use Kotlin's error mechanism. Is it to take advantage of this?
The same guide also says its possible to use Result:Note: Another way to model the result of interactions with the data layer is by using a Result class. This pattern models errors and other signals that can happen as part of processing the result. In this pattern, the data layer returns a Result<T> type instead of T, making the UI aware of known errors that could occur in certain scenarios. This is necessary for reactive programming APIs that don't have proper exception handling, such as LiveData.
This is necessary for reactive programming APIs that don't have proper exception handling, such as LiveData.
Because of the above statement, I had interpreted that if there is no error mechanism such as LiveData, I should use Result, and if there is an error mechanism such as coroutines, I should use that mechanism.
7 months ago
Result is not necesarry for coroutines, but it is still a better option than exceptions (IMO)
7 months ago
Opinions vary on this one; I don't think there's much value (in kotlin) in the usual generic
that's either a wrapped result or a Throwable outside of infrastructure code that pipes a
result into a
or something like that, to redirect the result to another call stack more appropriate for handling it. More domain-specific error states emitted via a LiveData or StateFlow can be more appropriate in context, but
is the result of a specific operation in time and it doesn't make a very good state object.
In terms of API design of individual functions/methods and deciding how it should report failures, there's a lot to consider. What does it mean for your particular operation to fail safe? Do you need to ensure that calling code doesn't march forward with incomplete/missing data that it can't handle? What are the consequences in context for ignoring an error? Where should any associated retry infrastructure live?
usually things like
type API shapes handle the cases where the caller is in a position to make a decision on how to proceed if the operation can't complete its requested purpose; a stack trace and debugging message is overkill
blocks clean up as necessary, but often when you're deep enough in a call stack where an error happens, you're not in a position to do something intelligent about it until several layers up. Asking your callers to write multiple layers of manual error propagation is kind of silly. That's what exceptions are for.
is even more powerful as a resource cleanup mechanism.
If you take result types for single operations to their limit, you end up with things like railway-oriented programming which turns all code you write into more or less RxJava operator chains, trying to duplicate language features with bespoke API
and if you try this, do it in a hobby project so that you can quietly rip it all out after 4-6 weeks of living with it without inflicting it on anyone else who has to work in that codebase 😛
Joking aside, the style can work very well in some languages; Rust's result type and the language support for it are considered quite good. But exceptions fit very well with the rest of Kotlin's language design
7 months ago
Rust’s result type and the language support for it are considered quite good
It considered by some as good, but honestly I didn’t find it very good in terms of how easy to use it.
It’s safe out of the box, it’s true, but because it’s very hard to combine multiple error types safely, imagine you have a function which does network request, save it to database and do some internal validation, if you want to propagate all those errors you need a custom Result type with all the cases combined and also resulting code doesn’t look very well, you sometimes even need intermediate result types just for you own code to propagate results in a type safe wayI understand why it was done this way, and I like idea itself, I just found that it quite tedious to work in this style, and this is the reason why so many Rust code which I see just doing .unwrap() + Result block with generic Error typeThis why things like
library exist to wrap any errorI think it’s completely solvable with better API for errors, but at least what I see there is no simple way to do this using std libI have very limited experience with Rust, maybe I just was not went deep enough and there are better approaches
6 months ago
Thanks. I took some time to think about it and I understand that there is a problem with Results and check exceptions.
I thought it might be better to avoid using things like Result, which can be a problem on the user's side, and usually allow exceptions to be handled in common logic to prevent them from not being handled, and only handle them properly when they need to be handled individually.