
哈Ha!
每个人都喜欢运行时异常。 没有更好的方法来发现编写代码时未考虑某些因素。 尤其是-如果异常使数百万用户中的应用程序中断,并且此消息来自分析门户网站时发出恐慌电子邮件。 星期六早上。 乡村旅行时。
之后,您认真考虑错误处理-Kotlin为我们提供的可能性是什么?
首先想到的是try-catch。 对我来说-一个不错的选择,但是有两个问题:
- 毕竟这是一个额外的代码(代码周围的强制包装不会以最佳方式影响可读性)。
- 并非总是(尤其是在使用第三方库时)从catch块中获取有关导致错误的确切原因的信息。
让我们看看尝试解决上述问题时try-catch将代码转换成什么。
例如,最简单的网络查询执行功能
fun makeRequest(request: RequestBody): List<ResponseData>? { val response = httpClient.newCall(request).execute() return if (response.isSuccessful) { val body = response.body()?.string() val json = ObjectMapper().readValue(body, MyCustomResponse::class.java) json?.data } else { null } }
变得像
fun makeRequest(request: RequestBody): List<ResponseData>? { try { val response = httpClient.newCall(request).execute() return if (response.isSuccessful) { val body = response.body()?.string() val json = ObjectMapper().readValue(body, MyCustomResponse::class.java) json?.data } else { null } } catch (e: Exception) { log.error("SON YOU DISSAPOINT: ", e.message) return null } }
有人可能会说:“这还不算太糟,您和您的Kotlin都希望所有代码都加糖,”他补充说(这是一个引号),然后他会……两次正确。 不,今天不会有任何抱怨-每个人都自己决定。 我亲自统治了自写json解析器的代码,其中每个字段的解析都包装在try-catch中,而每个catch块都是空的。 如果有人对这种状况感到满意,请在他的手中举起旗子。 我想提供一种更好的方法。
大多数类型化的函数式编程语言提供了两种用于处理错误和异常的类:
Try和
Either 。 尝试处理异常,或者尝试处理业务逻辑错误。
Arrow库允许您将这些抽象与Kotlin一起使用。 因此,您可以按以下方式重写上述请求:
fun makeRequest(request: RequestBody): Try<List<ResponseData>> = Try { val response = httpClient.newCall(request).execute() if (response.isSuccessful) { val body = response.body()?.string() val json = ObjectMapper().readValue(body, MyCustomResponse::class.java) json?.data } else { emptyList() } }
这种方法与使用try-catch有何不同?
首先,任何在您之后(而且很可能会)阅读此代码的人都已经能够通过签名理解执行该代码可能导致错误-并编写用于处理该代码的代码。 此外,如果不这样做,编译器会发誓。
其次,在如何处理错误方面具有灵活性。
在Try中,执行的错误或成功分别表示为Failure和Success类。 如果我们希望函数始终在出错时返回某些内容,则可以设置默认值:
makeRequest(request).getOrElse { emptyList() }
如果需要更复杂的错误处理,可以进行折叠:
makeRequest(request).fold( {ex ->
您可以使用restore函数-如果Try返回Success,则其内容将被完全忽略。
makeRequest(request).recover { emptyList() }
如果需要通过在Try上调用.monad()工厂来使用一系列命令来处理Success结果,则可以用于理解(由Scala的Arrow创建者借用)。
Try.monad().binding { val r = httpclient.makeRequest(request) val data = r.recoverWith { Try.pure(emptyList()) }.bind() val result: MutableList<Data> = data.toMutableList() result.add(Data()) yields(result) }
上面的选项可以在不使用绑定的情况下编写,但是将以不同的方式进行读取:
httpcilent.makeRequest(request) .recoverWith { Try.pure(emptyList()) } .flatMap { data -> val result: MutableList<Data> = data.toMutableList() result.add(Data()) Try.pure(result) }
最后,可以使用以下情况处理函数的结果:
when(response) { is Try.Success -> response.data.toString() is Try.Failure -> response.exception.message }
因此,使用Arrow,您可以用灵活且非常方便的方式替换远离理想的try-catch结构。 使用Arrow的另一个优点是,尽管该库将自身定位为功能,但您可以从那里使用单个抽象(例如,相同的Try),同时继续编写良好的旧OOP代码。 但是我警告您-您可能会喜欢并参与其中,在接下来的几周中,您将开始学习Haskell,而您的同事很快就会停止了解您对代码结构的推理。
PS:值得:)