开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 24 天,点击查看活动详情
用Kotlin Result编程: 返回Result
如何通过返回值来解决异常的所有问题. 对Result类的介绍, 以及它如何使处理意外错误变得轻而易举. 关于它与实际函数式编程的关系的简要说明.
以下是我们想要解决的具体问题:
- 我们想保留正常的控制流. 这意味着我们不希望执行过程从发生错误的地方跳到一千光年之外.
- 我们要处理所有的错误状态, 而不仅仅是"典型"的错误.
- 我们希望尽可能地将我们的代码与外部代码抛出的特定异常解耦.
- 我们想写更少的代码.
虽然这看起来是个很高的要求, 但实际上并非如此.
这里有一些观察:
- 当我们通过返回一个值来退出函数时, 正常的控制流得以保留. 当我们停止抛出异常时, 我们就没有其他方法来退出函数了, 所以当一个异常被抛出时, 我们需要尽快停止重新抛出它. 由于异常几乎无一例外(哈), 最终都会被转换为数值, 我们真正要说的是, 我们需要这种转换尽可能地发生在错误发生的地方 - 也就是说, 我们需要将进行数值转换的代码尽可能地深入.
- 1.在99%的情况下,你所做的只是将
Throwable转化为一个字符串表示,并被发送到客户端(也许还被写入日志). 出于这个原因, 我们可以直接捕获Throwable(这实际上是可以做到的, 有某些限制), 而不必担心其具体类型. - 1.剩下的1%的情况是特定业务需求的结果, 这使得它们成为业务(即服务)逻辑的一部分. 如果一个业务需求是"当具体的错误X发生时,做Y", 那么编写与X被抛出紧密耦合的服务代码是绝对可以的, 只要它是在所有其他业务逻辑旁边实现.
所以, 让我们从重新抛出异常切换到返回值. 要做到这一点, 我们需要一个数据类型来表示两种情况.
- 代码顺利完成, 结果是一些
value: T, 以及 - 发生了一个oops.
首先想到的是Optional, 它在语义上等同于T?, 然而这对我们来说还不够丰富. Nullables/optionals可以用来表示成功, 但如果出了问题, 我们可能想编码一些关于出错的信息 - 可以说是"oops实例".而nullables/optionals只有传达"有"与"无"的能力. 我们想要的是Success(value: T)和Failure(oops: Throwable).
所以...让我们来做这个! 让我们创建一个类型Result和两个子类Success(val value: T) : Result和Failure(val error: Throwable) : Result, 并使用这些来包装我们所有的计算.
sealed interface Result<out T>
data class Success<T>(val value: T): Result<T>
data class Failure(val error: Throwable) : Result<Nothing>
// Before
fun retrieve(id: Long): Product = try {
productRepository.retrieveById(id)
} catch (e: ObjectNotFoundException) {
throw ProductIdDoesntExist(e)
}
// after
fun retrieve(id: Long): Result<Product> = try {
Success(productRepository.retrieveById(id))
} catch (e: Throwable) {
Failure(e)
}
嗯...我们可以做得更好一些吗? 是的, 可以:
fun <T> runCatching(block: () -> T): Result<T> = try {
Success(block())
} catch (e: Throwable) {
Failure(e)
}
fun retrieve(id: Long): Result<Product> = runCatching {
productRepository.retrieveById(id)
}
未完, 待续...
用Kotlin Result编程: 返回Result - 2
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 24 天,点击查看活动详情