我想知道我的方法的特征是什么,这样我就可以优雅地处理不同类型的失败。
这个问题在某种程度上是我对Scala中错误处理的许多问题的总结。您可以在此处找到一些问题:
目前,我理解如下:
存储库层
现在请考虑我有一个< code >用户存储库。< code>UserRepository存储用户并定义一个< code>findById方法。可能会发生以下故障:
此外,用户可能丢失,从而导致 Option[User]
结果
使用存储库的JDBC实现,可以抛出SQL的、非致命的异常(违反约束或其他),因此使用try是有意义的。
当我们处理 IO 操作时,如果我们想要纯函数,IO monad 也是有意义的。
因此结果类型可能是:
尝试[选项[用户]]
IO[选项[用户]]
服务层
现在,让我们介绍一个业务层,UserService
,它提供了一些方法updateUserName(id,newUserName)
来使用存储库先前定义的findById
。
可能发生以下故障:
则结果类型可以是:
尝试[要么[业务错误,用户]]
IO[要么[业务错误,用户]]
这里的BusinessError不是可抛出的,因为它不是异常的失败。
用于理解
我希望继续使用for-comprehensive来组合方法调用。
为了理解,我们不能简单地将不同的单子混合在一起,所以我想我应该为我的所有操作提供某种统一的返回类型,对吗?
我只是想知道,在你的现实世界中,Scala应用程序是如何成功的,以便在发生不同类型的故障时继续使用理解。
目前,对于理解对我来说工作得很好,使用服务和存储库都返回要么[Error, Result]
,但所有不同类型的故障都融合在一起,处理这些故障变得有点棘手。
您是否定义了不同种类的monad之间的隐式转换,以便能够使用for-comedions?
您是否定义了自己的 monad 来处理故障?
顺便说一下,也许我很快就会使用异步IO驱动程序。所以我想我的返回类型可能会更复杂:IO[Future[要么[BusinessError, User]]]
任何建议都是受欢迎的,因为我真的不知道使用什么,而我的应用程序并不花哨:它只是一个API,我应该能够区分可以显示给客户端的业务错误和技术错误。我试图找到一个优雅而纯粹的解决方案。
更新2[2020-09]:自从该答案首次被编辑以来,scala生态系统发生了一些演变。cat-效3
正在谈论拥有一个专用的错误通道[更新2021-03:它最终选择不这样做],calaz 8
停滞不前,一个新的库出现了:ZIO
,一个核心具有双仿函数IO monad(一个依赖注入系统,超出了当前问题的范围)的库在scala中获得了关注。
由于它有专门的错误通道,刚打到v1.0.0,题目还比较新颖,所以我回答了问题(和这个有关):ZIO错误通道是什么,如何获得一个关于里面放什么的感觉?。
它还处理了更一般的问题(如:发现应用程序的故障模式并处理它们,让futur dev/adminsys/用户在行为上有代理权,即使是在出现错误的情况下),并简要总结了我的演讲:应用程序中的系统错误管理。希望它能有所帮助,并为这个(大而复杂的)话题提供更多的背景。
@pthariens-flame的回答很棒,你应该把它用在你手头的任务上。
我想带来一些该领域最近发展的背景知识,所以这只是一个一般性的信息回答。
错误管理基本上是我们的头号工作,dev。快乐的道路是快乐和无聊的,它不是用户会抱怨的地方。大多数(所有?)问题在于过程中隐含的效果(尤其是I/O)。
管理问题的一种方法是遵循通常所说的“纯FP approch”,在程序的纯/完全和不纯/非全部部分之间画一条大红线。这样做时,您可以利用这种可能性来干净利落地处理错误。
最近(18个月?),Scala在该领域进行了大量的研究和开发。实际上,我相信Scala是当今所有语言中在这个非常具体的问题上最令人兴奋和最具破坏性的地方(但当然,它可能只是对可用性/最新信息的大脑偏见)。
Scalaz8、Monix和cat效果是这一快速发展的三个主要贡献者。因此,与这三个项目相关的任何内容(会议演讲、博客文章等)都将帮助您了解正在发生的事情。
因此,为了让故事保持简短,Scalaz8将改变IO建模的方式,以更好地考虑错误管理。约翰·德戈斯(John DeGoes)领导了这项工作,他在这方面提供了一些很好的资源:
文章:
视频:
Monix和猫效应也有很多东西,但我相信这个主题的大部分资源都发生在相应项目中的拉取请求中。
亚历山德鲁·内德尔库在一次谈话中介绍了这些问题一些背景情况:
亚当·沃斯基(Adam Warski)在这里进行了比较:
最后,Luka Jacobowitz为猫部分撰写了一篇出色的文章:“Rethinking MonadError”https://typelevel.org/blog/2018/04/13/rethinking-monaderror.html 它用另一盏灯覆盖了很多相同的领域。
[编辑]:正如同行所注意到的那样,该领域(r)进化的跨度并不止于scala-land的跨度。有一项工作要做很多工作,试图使效果编码(IO等)更高性能。该领域的最新步骤是尝试使用Kleisli Arrows代替monads,以最大限度地减少JVM上的GC流失。
看:
希望有帮助!
更新[2018-07]:reddit上有一个关于这个主题的长而有趣的帖子:“有人可以向我解释IO的好处吗?”https://www.reddit.com/r/scala/comments/8ygjcq/can_someone_explain_to_me_the_benefits_of_io/
约翰·德戈斯的贡献:“Scala战争:FP-OOP vs FP”http://degoes.net/articles/fpoop-vs-fp
这就是 Scalaz 的 EitherT
monad 变压器的用途。IO[Either[E, A]]
堆栈等效于AoseT[IO, E, A]
,除了前者必须按顺序作为多个monad处理,而后者自动成为单个monad,它将任
一功能添加到基本monad IO
。同样,您可以使用 AnyT[Future, E, A]
向异步操作添加非异常错误处理。
一般来说,单声道变换器可以满足在单个中混合多个单声道以进行理解和/或单声道操作的需要。
编辑:
我假设您使用的是 Scalaz 版本 7.0.0。
为了在< code>IO
monad之上使用< code>EitherT monad转换器,首先需要导入Scalaz的相关部分:
import scalaz._, scalaz.effect._
您还需要定义您的错误类型:< code>RepositoryError、< code>BusinessError等。这照常工作。例如,您只需要确保可以将任何< code>RepositoryError转换为< code>BusinessError,然后进行模式匹配以恢复错误的确切类型。
那么您的方法的签名就变成了:
def findById(id: ID): EitherT[IO, RepositoryError, User]
def updateUserName(id: ID, newUserName: String): EitherT[IO, BusinessError, User]
在每个方法中,您可以将基于 EitherT
和 IO
的 monad 堆栈用作单个统一的 monad,并像往常一样用于
理解。AnyT
将负责在整个计算过程中对基本monad(在本例中为IO
)进行线程化,同时还像Auly
通常那样处理错误(除了默认情况下已经右偏置,因此您不必不断处理所有常见的.right
垃圾)。当您想要执行 IO
操作时,您所要做的就是通过在 IO
上使用 liftIO
实例方法将其提升到组合的 monad 堆栈中。
顺便说一下,当以这种方式工作时,EitherT
伴随对象中的函数可能非常有用。
Weex 通用错误结果页,主要包括通用错误、无商品、无网络、定位错误的错误情况 规则 用于出错的页面,减少用户焦虑感 配置 type.js#L5 来定制化 Demo 使用方法 <template> <wxc-result type="errorPage" :show="show" padding-top="232"
resultMap元素是iBATIS中最重要和最强大的元素。 您可以使用iBATIS ResultMap减少高达90%的JDBC编码,在某些情况下,它允许您执行JDBC甚至不支持的操作。 ResultMaps的设计使得简单语句根本不需要显式结果映射,而更复杂的语句只需要描述关系的绝对必要。 本章仅提供iBATIS ResultMaps的简单介绍。 我们在MySQL中有以下EMPLOYEE表 - C
API 接口开发返回结果规范化解决方案 一、概述 Api Result,是 RESTful风格的API接口响应参数规范化的一套解决方案 。它是在实际应用环境下产生了,并不断更新和完善。 结合Spring Boot进行开发,让接口响应结果变得更加规范。 二、示例 { "code":"111111", "message":"Query Success", "success":true
当我们要重复多次使用特定的 Result 类型怎么办呢?回忆一下,Rust 允许我们创建别名。对问题中提到的特定 Result,我们可以很方便地给它定义一个别名。 在单个模块的级别上创建别名特别有帮助。在特定模块中发现的错误常常会有相同的 Err 类型,所以一个单一的别名就能简便地定义所有的关联 Result。这点太重要了,甚至标准库也提供了一个: io::Result! 下面给出一个快速示例来展
前面关于 panic 例子,提供给我们的是一个无用的错误消息。为了避免这样,我们需要更具体地指定返回类型。在那个例子中,该常规元素为 i32 类型。 为了确定 Err 的类型,我们可以借助 parse(),它使用 FromStr trait 来针对 i32 实现。结果是,Err 类型被指定为 ParseIntError。 在下面例子中要注意,使用简单的 match 语句会导致更加繁琐的代码。事实证
使用匹配链接结果会得到极其繁琐的内容;幸运的是,? 运算符可以使事情再次变得干净漂亮。? 运算符用在返回值为 Result 的表式式后面,等同于这样一个匹配表式,其中 Err(err) 分支展开成提前(返回)return Err(err),同时 Ok(ok) 分支展开成 ok 表达式。 mod checked { #[derive(Debug)] enum MathError {