在scala中选择正确的异常处理

时间:2015-03-18 18:43:15

标签: scala exception-handling coding-style

我是Scala的新手,对于处理异常的各种方法以及寻找有关该主题的最佳实践建议感到有些困惑。我正在编写一个使用现有阻止SDK检索客户的简单方法。可能的结果是:

  1. 找到客户
  2. 未找到客户(从SDK返回为NotFoundException)
  3. 与远程服务器通话时出错(SDK引发其他一些异常)
  4. 所以我希望我的方法有一个返回类型Future[Option[Customer]],并返回上面的每个案例:

    1. 成功的未来:一些(客户)
    2. 成功的未来:无
    3. 未来失败
    4. 以下是我使用try / catch编写的内容:

      private def findCustomer(userId: Long): Future[Option[Customer]] = future {
        try {
          Some(gateway.find(userId))
        } catch {
          case _: NotFoundException => None
        }
      }
      

      这样做很好,对我来说似乎很干净,但似乎并不是真正的“Scala方式” - 我被告知要避免使用try / catch。所以我一直在寻找一种方法来改为使用Try重写它。

      以下是两种变体(我认为)使用Try表现完全相同。

      变式A:

      private def findCustomer(userId: Long): Future[Option[Customer]] = future {
        Try(
          Some(gateway.find(userId))
        ).recover {
          case _: NotFoundException => None
        }.get
      }
      

      变式B:

      private def findCustomer(userId: Long): Future[Option[Customer]] = future {
        Try(
          Some(gateway.find(userId))
        ).recover {
          case _: NotFoundException => None
        }
      } flatMap {
        case Success(s) => Future.successful(s)
        case Failure(f) => Future.failed(f)
      }
      

      我不是A的忠实粉丝(尽管它比B更简洁),因为.get似乎有点奸诈。 B绝对是最明确的,但将Try个案例映射到相应的Future结果似乎很无聊。

      经验丰富的Scala程序员如何写这个?

4 个答案:

答案 0 :(得分:5)

我认为使用try/catch的初始版本非常好,因为它是现有SDK的包装。

或者,您可以使用recover上的Future方法:

def findCustomer(userId: Long): Future[Option[Customer]] =
  Future(Option(gateway.find(userId))).recover {
    case e: NotFoundException => None
  }

答案 1 :(得分:2)

一种选择是避免链接Try和Future。从某种意义上说,未来是异步尝试。

您可以直接使用Future [Customer]并将NotFoundException视为要从而不是None值进行恢复。通常情况下,您可以将操作链接到未来本身,而无需处理故障情况(通过映射等)。这一切都取决于你将如何使用它(未来完成时你的逻辑如何分支)。换句话说,也许它似乎对你来说很复杂,因为它是,并且你强迫它通过强制Future [Option [Customer]]返回类型。

能够连接多个操作并继续进行计算,当且仅当所有这些都是正确的Scala功能时(特别是对于理解等)。

答案 2 :(得分:1)

您可能正在寻找:

Future.fromTry(myTry)

因此,您可以这样做:

Future.fromTry {
    Try(Some(gateway.find(userId))).recover {
        case _: NotFoundException => None
    }
}

仅捕获NotFoundException,就像您的解决方案一样。这里唯一的问题是该方法不会异步执行。如果有必要,请考虑按照Ionut的建议使用Future.recover

另一个值得一提的惯用选项是使用Either[F, S],其中S是成功返回的类型,F可以保存错误(您可能希望传播) 。因此,您可以使用Either[ Exception, Option[String]]Either[String, Option[String]],其中第一个String是错误消息。

答案 3 :(得分:0)

如果您准备采用某些scalaz。与scalaz脱节在处理错误情景时非常有用和自然。这就像scala Either但scalaz disjunction \/是正确的偏见。你将获得正确的成功价值和左边的例外。用\/.fromTryCatch包装代码块会在析取的左侧返回异常。权利总是有成功价值。映射over disjunction比Scala更容易,因为析取是正确的偏差,并且很容易从右边给你价值。

import scalaz.\/

private def findCustomer(userId: Long): Future[\/[Throwable,Option[Customer]] ]= future {
  \/.fromTryCatch {
    Some(gateway.find(userId))

  }
}