何时以及如何在 Kotlin 中使用 Result?

Som*_*era 40 kotlin

我喜欢 Result 的想法。\n我喜欢封装 try/catch。

\n

但我\xe2\x80\x99m 对于如何以及何时使用 Result 有点困惑。

\n

我目前这样使用它:

\n

我的适配器和服务返回结果。记录失败和堆栈跟踪,但不执行其他操作

\n
runCatching{\n    .... // do something cool \n}.onFailure {\n    logger.error("Something bad happened ", it)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我的资源类折叠并处理结果

\n
return service.method().fold(\n    onFailure = {\n        Response.serverError().entity("Oops").build()\n    },\n    onSuccess = {\n        Response.ok().entity(doSomethingWith(it)).build()\n    }\n)\n
Run Code Online (Sandbox Code Playgroud)\n

这真的是使用结果的正确方法吗?或者有更惯用的 Kotlin 编码方式吗?

\n

Jof*_*rey 64

TL;DR:永远不要在业务代码中(更喜欢自定义密封类),但是您可以考虑Result是否构建一个必须通过与异常机制不同的方式中继各种错误的框架(例如 kotlinx.coroutines 的实现)。


首先,如果您觉得有趣的话,实际上有一个用例列表来说明最初引入 的动机。Result也在同一文档中:

Result 类旨在捕获 Kotlin 函数的一般失败以供后续处理,并且应该在通用 API(如 futures 等)中使用,这些 API 处理 Kotlin 代码块的调用,并且必须能够表示成功和失败执行结果。Result 类并非旨在表示特定于域的错误条件。

另外,这是 Roman Elizarov 撰写的一篇非常好的文章,介绍了 Kotlin 中的异常,更具体地说,何时使用类型系统而不是异常。

以下大部分内容是我个人的观点。它是根据事实建立的,但仍然只是一种观点,所以请持保留态度。

不要在业务代码中使用runCatching或捕获Throwable

请注意,runCatching它捕获各种类型的Throwable,包括 JVM Error,例如NoClassDefFoundErrorThreadDeathOutOfMemoryErrorStackOverflowError。通常,您对此类 JVM 错误几乎无能为力(甚至报告它们也可能是不可能的,例如在 OOME 的情况下)。

根据 Java 文档,Error“表明合理的应用程序不应尝试捕获的严重问题”。在一些非常特殊的情况下,您可能希望尝试从错误中恢复,但这是非常特殊的(双关语)。

通常不建议使用像这样的包罗万象的机制(甚至catch(Exception)),除非您正在实现某种需要尝试以某种方式报告错误的框架。

因此,如果我们不捕获错误(而是让它们自然地冒出来),Result则不是此处图片的一部分。

不要捕获业务代码中的编程异常

除了 JVM 错误之外,我认为由于编程错误而导致的异常也不应该以导致业务代码膨胀的方式进行真正的处理。在正确的地方使用error(), check(),require()将利用异常来检测编译器 ( IllegalStateException, IllegalArgumentException) 无法捕获的错误。在业务代码中捕获这些异常通常没有意义,因为当程序员犯了错误并且代码逻辑被破坏时,它们就会出现。您应该修复代码的逻辑。

有时,捕获代码区域周围的所有异常(包括这些异常)并通过对整个失败操作进行高级替换来恢复(通常在框架代码中,但有时也在业务代码中)可能仍然有用。不过,这可能会通过 some 来完成try-catch(Exception),但Result这里不会涉及,因为此类代码的目的是用try-catch可以替换的高级操作进行定界。低级代码不会返回,Result因为它不知道是否有更高级别的操作可以在出现编程错误时用某些东西替换,或者它是否应该冒泡。

建模业务错误

这就给类似结果的类型留下了业务错误。我所说的业务错误是指缺少实体、来自外部系统的未知值、错误的用户输入等。但是,我通常会找到比使用更好的方法来对它们进行建模kotlin.Result(正如设计文档所规定的那样,这并不是为了这个目的)。使用可为 null 的类型对值缺失进行建模通常很容易fun find(username: String): User?。可以使用涵盖不同情况的自定义密封类来对一组结果进行建模,例如结果类型但具有特定的错误子类型(以及关于错误的更有趣的业务数据,而不仅仅是Throwable)。

所以总之,最终我从来不在kotlin.Result业务代码中使用自己(我可以考虑将其用于需要报告所有错误的通用框架代码)。


我的适配器和服务返回结果。记录失败和堆栈跟踪,但不执行其他操作

关于这一点的旁注。正如您所看到的,您正在记录服务本身的错误,但从服务使用者的角度来看并不清楚。消费者收到一个Result,那么谁负责处理这里的错误呢?如果它是可恢复的错误,那么将其记录为错误可能合适,也可能不合适,也许作为警告更好,或者根本不记录。也许消费者比服务更了解问题的严重性。此外,该服务在记录 JVM 错误、编程错误(IAE、ISE 等)和业务错误的方式上没有区别。

  • 好吧,我看到了冗长的缺点。但对我来说,结果不能成为事实上的解决方案,这似乎是一种耻辱。Result 有一堆运算符,可以在各种用例中简洁地使用,而且它也非常轻量级。密封类很容易创建,但使用起来很冗长。针对特定情况的 Result 的等效项创建起来很冗长,但易于使用。无论如何,这样的讨论不是 StackOverflow 的主题。我可能会浏览 Kotlinlang 论坛,看看有关该主题的内容。 (4认同)