【问题标题】:Picking the right exception handling in scala在 scala 中选择正确的异常处理
【发布时间】:2015-03-19 20:10:41
【问题描述】:

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

  1. 找到客户
  2. 找不到客户(从 SDK 返回 NotFoundException)
  3. 与远程服务器通信时出错(SDK 抛出一些其他异常)

所以我希望我的方法有一个返回类型Future[Option[Customer]],并为上述每种情况返回:

  1. 成功的未来:一些(客户)
  2. 成功的未来:无
  3. 失败的未来

这是我用 try/catch 写的:

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

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

这里有 2 个(我认为)行为完全相同的变体,使用 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 程序员会如何写这个?

【问题讨论】:

    标签: scala exception-handling coding-style


    【解决方案1】:

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

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

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

    【讨论】:

    • 漂亮!我认为Future { ... } recover { ... } 正是我所寻找的,简洁、富有表现力和惯用语。谢谢!
    • 这很好,但您应该更改 Some => 选项以防止 Some(null)。
    • @TaylorLeese 你是对的。我使用了Some,因为我知道当用户不存在时该方法会抛出,但使用Option 并没有什么坏处。
    【解决方案2】:

    一种选择是避免链接 Try 和 Future。从某种意义上说,future 是一个异步的 Try。

    您可以直接使用 Future[Customer] 并将 NotFoundException 视为可以从中恢复的东西,而不是 None 值。通常,您将在未来本身上链接操作,而无需处理失败案例(通过映射等)。这完全取决于您将如何使用它(未来完成时您的逻辑如何分支)。换句话说,也许它对您来说似乎很复杂,因为它是,并且您通过强制 Future[Option[Customer]] 返回类型来强制它。

    当且仅当一切顺利时,进行链式多个操作并继续计算的能力是这里的 Scala 特性(尤其是在理解等方面)。

    【讨论】:

      【解决方案3】:

      您可能正在寻找:

      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 是一条错误消息。

      【讨论】:

      • 这种方法(使用Future.fromTry)的问题是计算不是异步完成的。创建的Future已经完成。
      • 另外,Either[A, B] 的约定是让A 代表失败,B 代表成功。所以Either[F, S].
      • 关于它的个论点的事实让我觉得我不应该使用Either。我宁愿依赖一种内置的语言(比如Try,它已经允许返回成功或失败),而不是人们似乎不同意的约定:)谢谢你的指点!跨度>
      • 这很合理,但我只想指出,我实际上同意 Ionut 并且已经编辑了我的帖子。我认为该约定已被很好地接受。
      【解决方案4】:

      如果您准备采用一些scalaz。与 scalaz 的析取在处理错误场景时非常有用且自然。这就像 scala Either 但 scalaz disjunction \/ 是正确的偏见。您将在右侧获得成功价值,在左侧获得例外。用\/.fromTryCatch 包装代码块会在析取左侧返回异常。正确的永远有成功的价值。析取映射比 Scala 更容易,因为析取是正确的,并且很容易从正确的角度为您提供价值。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-07-24
        • 2023-03-07
        • 2021-05-22
        • 1970-01-01
        • 2013-10-02
        • 2016-08-21
        • 1970-01-01
        相关资源
        最近更新 更多