Kotlin 1.3.61
I have been reading the Effective Kotlin by Marcin Moskala
book. And found the item on handling errors interesting as it discourages using a try-catch block and instead custom handler class
Quote from the book:
Using such error handling is not only more efficient than the try-catch block but often also easier to use and more explicit
However, there are cases where a try-catch cannot be avoided. I have the following snippet
class Branding {
fun createFormattedText(status: String, description: String): ResultHandler<PricingModel> {
return try {
val product = description.format(status)
val listProducts = listOf(1, 2, 3)
ResultHandler.Success(PricingModel(product, listProducts))
}
catch (exception: IllegalFormatException) {
ResultHandler.Failure(Throwable(exception))
}
}
}
class PricingModel(val name: String, products: List<Int>)
So the description.format(status)
will throw an exception if it fails to format
This is my HandlerResult class, and what the book recommends:
sealed class ResultHandler<out T> {
class Success<out T>(val result: T) : ResultHandler<T>()
class Failure(val throwable: Throwable) : ResultHandler<Nothing>()
}
class FormatParsingException: Exception()
And how I use them in my code:
fun main() {
val branding = Branding()
val brand = branding.createFormattedText("status", "$%/4ed")
when(brand) {
is Success -> println(brand.result.name)
is Failure -> println(brand.throwable.message)
}
}
My question is. Is this one of those cases where a try-catch
cannot be avoided. Or could I still return a Failure if the format was to fail while not use the try-catch
?
Try/catch is used when an Exception can be thrown from the code, and you catch it in the catch-clause, which is an object oriented way to handle errors. You can't catch Exceptions with if/else blocks - they share nothing with try/catch.
In Kotlin, we use try-catch block for exception handling in the program. The try block encloses the code which is responsible for throwing an exception and the catch block is used for handling the exception. This block must be written within the main or other methods.
Kotlin try-catch block is used for exception handling in the code. The try block encloses the code which may throw an exception and the catch block is used to handle the exception. This block must be written within the method. Kotlin try block must be followed by either catch block or finally block or both.
We can't have catch or finally clause without a try statement. We can have multiple catch blocks with a single try statement. try-catch blocks can be nested similar to if-else statements. We can have only one finally block with a try-catch statement.
You can avoid the try-catch by using the Kotlin builtin Result class and code. (Behind the scenes, you have a try-catch - see source).
fun createFormattedText(status: String, description: String): ResultHandler<PricingModel> {
runCatching {
val product = description.format(status)
val listProducts = listOf(1, 2, 3)
ResultHandler.Success(PricingModel(product, listProducts))
}.getOrElse {
ResultHandler.Failure(it)
}
}
The topic of the chapter in the book is "Prefer null or Failure result when the lack of result is possible", so if you don't care about the exception, you can do this:
fun createFormattedText(status: String, description: String): PricingModel? {
runCatching {
val product = description.format(status)
val listProducts = listOf(1, 2, 3)
PricingModel(product, listProducts)
}.getOrNull()
}
For debugging/logging, that would also work:
fun createFormattedText(status: String, description: String): PricingModel? {
runCatching {
val product = description.format(status)
val listProducts = listOf(1, 2, 3)
PricingModel(product, listProducts)
}.onFailure {
log("Something wrong with $it")
}.getOrNull()
}
Unfortunately you can't replace your ResultHandler
with Kotlins Result
- because Result
can't be used as a return type. I found this post explaining the reasoning and also a workaround, hope it helps.
Alternatively you could build your own extension function for your ResultHandler
and move the handling of exceptions under the hood:
public inline fun <R> runCatching(block: () -> R): ResultHandler<R> {
return try {
ResultHandler.Success(block())
} catch (e: Throwable) {
ResultHandler.Failure(e)
}
}
I haven't read this book, but I imagine the author's point is that it's better to expose a sealed class result wrapper than an exception in your public functions, not that you should avoid using try-catch
on third-party or stdlib functions that can throw.
In your case, you have to use catch
to convert the exception into a result. I would say your code is fully implementing the author's advice. You can't help that the function you're calling is exposing a must-handle exception, but you can convert that into the better paradigm in your own public function's signature.
Kotlin did away with checked exceptions largely because of the problems they create. The whole call stack has to be exception-types aware in their signatures even if intermediate functions don't care about handling the errors, so this is very poor encapsulation and minor API changes can have a huge ripple effect. But removing checked exceptions creates the issue of a programmer possibly forgetting (or not knowing they need to) handle a possible error.
A sealed result wrapper class can alleviate both problems. Each function in the call stack can elect whether to pass the whole result back or intercept errors, and which types of errors to intercept. If they just pass it along, they don't need to know about the possible kinds of errors. None of the function signatures have to be modified if the error types are modified. And the programmer doesn't have anything they can forget to handle. Either they just pass the whole result along without looking at it, or they replace the error with a default value, or they can choose to actually respond to specific types of errors.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With