使用有效的 kotlin 方式在不使用 try-catch 块的情况下处理错误

ant*_*009 7 exception kotlin

科特林 1.3.61

我一直在看书Effective Kotlin by Marcin Moskala。并发现有关处理错误的项目很有趣,因为它不鼓励使用 try-catch 块,而是使用自定义处理程序类

引自书中:

使用这样的错误处理不仅比 try-catch 块更有效,而且通常也更容易使用和更明确

但是,在某些情况下无法避免 try-catch。我有以下片段

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>)
Run Code Online (Sandbox Code Playgroud)

所以description.format(status)如果格式化失败会抛出异常

这是我的 HandlerResult 类,以及本书推荐的内容:

sealed class ResultHandler<out T> {
    class Success<out T>(val result: T) : ResultHandler<T>()
    class Failure(val throwable: Throwable) : ResultHandler<Nothing>()
}

class FormatParsingException: Exception()
Run Code Online (Sandbox Code Playgroud)

以及我如何在我的代码中使用它们:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是。这是try-catch无法避免的情况之一吗?或者,如果格式在不使用时失败,我仍然可以返回失败try-catch吗?

Ten*_*r04 8

我没有读过这本书,但我想作者的观点是公开一个密封的类结果包装器比公开函数中的异常更好,而不是你应该避免try-catch在可以抛出的第三方或 stdlib 函数上使用。

在您的情况下,您必须使用catch将异常转换为结果。我会说你的代码完全实现了作者的建议。您无法帮助您调用的函数公开了一个必须处理的异常,但是您可以将其转换为您自己的公共函数签名中更好的范例。

Kotlin 废除了受检异常主要是因为它们带来了问题。即使中间函数不关心处理错误,整个调用堆栈也必须在其签名中意识到异常类型,因此这是非常糟糕的封装,微小的 API 更改可能会产生巨大的连锁反应。但是删除已检查的异常会导致程序员可能忘记(或不知道他们需要)处理可能的错误。

密封的结果包装类可以缓解这两个问题。调用堆栈中的每个函数都可以选择是将整个结果传回还是拦截错误,以及拦截哪些类型的错误。如果他们只是传递它,他们就不需要知道可能的错误类型。如果修改了错误类型,则无需修改任何函数签名。程序员没有任何可以忘记处理的事情。他们要么直接传递整个结果而不查看它,要么用默认值替换错误,或者他们可以选择实际响应特定类型的错误。


CFr*_*rei 8

您可以使用 Kotlin 内置的Result类和代码来避免 try-catch 。(在幕后,您有一个尝试 - 请参阅源代码)。

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

书中章节的主题是“Prefer null or Failure result when the missing result is possible”,所以如果你不关心异常,你可以这样做:

fun createFormattedText(status: String, description: String): PricingModel? {
    runCatching {
        val product = description.format(status)
        val listProducts = listOf(1, 2, 3)
        PricingModel(product, listProducts)
    }.getOrNull()
}
Run Code Online (Sandbox Code Playgroud)

对于调试/日志记录,这也适用:

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()
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,您不能ResultHandler用 Kotlins替换您的Result- 因为Result不能用作返回类型。我发现这篇文章解释了推理和解决方法,希望它有所帮助。

或者,您可以为您构建自己的扩展函数,ResultHandler并在后台移动异常处理:

public inline fun <R> runCatching(block: () -> R): ResultHandler<R> {
    return try {
        ResultHandler.Success(block())
    } catch (e: Throwable) {
        ResultHandler.Failure(e)
    }
}
Run Code Online (Sandbox Code Playgroud)