什么';什么是处理Scala中错误的推荐方法?

什么';什么是处理Scala中错误的推荐方法?,scala,exception,Scala,Exception,假设我有一个将用户添加到数据库的方法addUser。调用时,该方法可能: 成功 失败,因为输入无效(即用户名已存在) 失败,因为数据库崩溃或其他原因 该方法可能由单个数据库API调用组成,该调用在发生故障时会引发异常。如果是纯Java,我可能会捕获方法内部的异常并检查原因。如果它属于第二类(无效输入),我将抛出一个自定义的检查异常,解释原因(例如useralreadyexistException)。对于第二类,我只需重新抛出原始异常 我知道Java中有关于错误处理的强烈意见,所以可能有人不同意这

假设我有一个将用户添加到数据库的方法
addUser
。调用时,该方法可能:

  • 成功
  • 失败,因为输入无效(即用户名已存在)
  • 失败,因为数据库崩溃或其他原因
  • 该方法可能由单个数据库API调用组成,该调用在发生故障时会引发异常。如果是纯Java,我可能会捕获方法内部的异常并检查原因。如果它属于第二类(无效输入),我将抛出一个自定义的检查异常,解释原因(例如
    useralreadyexistException
    )。对于第二类,我只需重新抛出原始异常

    我知道Java中有关于错误处理的强烈意见,所以可能有人不同意这种方法,但我现在想重点讨论Scala

    所述方法的优点是,当我调用
    addUser
    时,我可以选择捕获
    useralreadyexistException
    并处理它(因为它适合我当前的抽象级别)但同时,我可以选择完全忽略可能引发的任何其他低级数据库异常,并让其他层处理它

    现在,我如何在Scala中实现同样的功能?或者什么是正确的Scala方法?显然,异常在Scala中的工作方式完全相同,但我发现有更好、更合适的方法

    据我所知,我可以选择
    选项
    尝试
    。然而,这两种情况都不像过去的好例外那样优雅

    例如,处理
    Try
    结果如下(借用自):

    最后两行代码正是我想要避免的,因为我必须将它们添加到任何我进行数据库查询的地方(而且指望
    MatchError
    似乎不是一个好主意)

    另外,处理多个错误源似乎有点麻烦:

    (for {
       u1 <- addUser(user1)
       u2 <- addUser(user2)
       u3 <- addUser(user3)
    } yield {
      (u1, u2, u3)
    }) match {
       case Success((u1, u2, u3)) => Ok(...)
       case Failure(...) => ...
    }
    
    前一种方法有我不知道的优点吗

    据我所知,
    Try
    在不同线程之间传递异常时非常有用,但只要我在单个线程中工作,它就没有多大意义


    我想听听关于这方面的一些论点和建议

    这个话题的大部分内容当然是意见问题。尽管如此,仍有一些具体的观点可以提出:

    • 您可以正确地观察到
      选项
      或者
      尝试
      都是非常通用的;这些名称没有提供太多文档。因此,您可以考虑一个定制的密封特性:

      sealed trait UserAddResult
      case object UserAlreadyExists extends UserAddResult
      case class UserSuccessfullyAdded(user: User) extends UserAddResult
      
      这在功能上相当于一个
      选项[User]
      ,并具有文档的附加好处

    • Scala中的异常总是未选中的。因此,您将在Java中使用未检查异常的相同情况下使用它们,而不是在使用已检查异常的情况下使用它们

    • 存在一元错误处理机制,例如,或

      这些一元工具的主要目的是供调用者一起组织和处理几个异常。因此,让您的方法返回这些类型,无论是在文档方面还是在行为方面,都没有多大好处。任何想要使用
      Try
      Validation
      的调用方都可以将方法的返回类型转换为所需的一元形式


    正如您可能从我的措辞中猜到的,我倾向于定义定制的密封特征,因为这提供了最好的自文档化代码。但是,这是品味的问题。

    谢谢你的回答。所以你会建议我用这个“包装”的返回值替换选中的异常,并保留未选中的异常,对吗?托比克:这是一个很好的问题,但我现在还没有准备好回答。我鼓励您查看
    验证的文档
    ,了解如何单独处理错误。不过,在我看来,对结果进行手动模式匹配并不是一个坏的解决方案。另一个问题是,使用模式匹配处理错误会大大提高嵌套级别。我想这在某种程度上是函数方法的必然结果。。。
    try {
         u1 = addUser(user1)
         u2 = addUser(user2)
         u3 = addUser(user3)
         Ok(...)
    } catch {
         case (e: UserAlreadyExistsException) => ...
    }
    
    sealed trait UserAddResult
    case object UserAlreadyExists extends UserAddResult
    case class UserSuccessfullyAdded(user: User) extends UserAddResult