25

ScalaプロジェクトのJavaライブラリを使用しているとしましょう。そのJavaライブラリは例外をあちこちにスローしますが、Scalaメソッドがスローできる例外を確認する方法がないため(ドキュメント化する場合を除いて)、「Scalaの世界で」それらを伝播させるだけでは快適ではありません。だから、これは私が書く傾向がある種類のコードです:

def doesNotThrowExceptions(parameter: String): Either[Throwable, T] =
  catching(classOf[IOException], classOf[NoSuchAlgorithmException]) either {
    // Code calling the Java library
    // Code generating a value of type T
  }

次に、通常、Either.RightProjection.flatMap戻るメソッドをチェーンするか、戻るEither[Throwable, ...]メソッドと他のメソッドEither.RightProjection.mapを混在させるために使用します。Either[Throwable, ...]または単に値Either.foldで何かをするためにThrowable。どういうわけか、しかし、これはまだ完全に正しいとは感じていません。

これは、ScalaでJavaの例外を処理するための最も「慣用的な」方法ですか?もっと良い方法はありませんか?

4

2 に答える 2

23

Java の例外に対処するための最も慣用的な方法が1 つあるかどうかはわかりません。例外がスローされるケースが少なくとも 4 つあります。

  1. あなたもライブラリの設計者も、うまくいかないとは本当に思っていなかった何かがうまくいかなかったのです。
  2. ライブラリの設計者が望んでいた機能が機能しなかったため、詳細を知る必要があります。
  3. うまくいくと思っていたことがうまくいきませんでした。詳細を知る必要はありません。
  4. メソッドは値を生成する場合と生成しない場合があり、例外をスローすることでそれを伝えます。

ベスト プラクティスは、これらのケースごとにほぼ間違いなく異なります。

1. 本当に例外的な例外

Scala には完全な機能の例外処理機能があります。何らかの対処ができるレベルになるまで、予期しない例外がキャッチされずに伝播するのを放置しても問題はありません。考えられるすべての例外をEither缶にパッケージ化すると、多くの時間が無駄になります。処理していないことがわかっていることを文書化し、適切な高レベルで try/catch を使用します (たとえば、saveEverythingメソッドはおそらく try/catch ブロックに入れる (またはその内容を 1 つにラップする) 必要があります。すべてを保存するのに失敗した場合は、単に死ぬだけでなく、状況を救おうとする可能性があります)。

特に、おそらくこの方法で処理し、すべての ではなくErrorのみを にパッケージ化する必要があります。ExceptionThrowableEither

2. 知っておくべき例外

これはあなたが話しているケースであり、それらに対処する方法についてすでにいくつかの良い提案をしています。catching既にお気付きのように、 を使用して例外を にパッケージ化できますEither。その後、次のこともできます

a. Eitherパターン マッチングを使用すると、より深く分解できます。

doesNotThrowExceptions("par").right.map(transformData) match {
  case Left(ioe: IOException) => /* ... */
  case Left(nsae: NoSuchAlgorithmException) => /* ... */
  case Right(x) => /* ... */
  case Left(e) => throw e  // Didn't expect this one...
}

b. Optionエラーをログに記録した後にダウンコンバートします。

doesNotThrowExceptions("par").left.map{ e =>
  println("You're not going to like this, but something bad happened:")
  println(e)
  println("Let's see if we can still make this work....")
}.right.toOption

c. 例外がフロー制御の非常に重要な部分であるEither場合、十分ではない可能性があります。代わりに、 and以外のものを使用して、独自の のEitherようなクラスを定義したい場合があります。または、次の左側をネストできます。LeftRightEither

try { Right(/* java code */) }
catch {
  case ioe: IOException => Left(Left(ioe))
  case nsae: NoSuchAlgorithmException => Left(Right(nsae))
}

d. ScalazValidationを使用します。これはよく似Eitherていますが、もう少し例外処理に合わせて調整されています。

3. 何かがうまくいかなかったことだけを知る必要がある例外

4. 戻り値がないことを示すためにスローされる例外

これらは概念的には 2 つの異なるカテゴリですが、同じ方法で処理します。

catching(classOf[IOException], classOf[NoSuchAlgorithmException]) opt { ... }

バックを取得しOptionます。次に、、、mapなどflatMap

于 2012-02-15T11:33:14.273 に答える
6

scalaz.Validation私はいつもよりも好きscala.Eitherです。この 2 つは代数的に同一の構造ですが、前者の方が機能的に豊富です (より便利なメソッドと型クラスのインスタンスがあります)。

Chris Marshall による素晴らしいプレゼンテーションやその他の Scalaz グッズについては、このリンクをチェックしてください。scalaz.Validation

従来の例外処理メカニズムを使用するコードと対話するために、次のエンリッチ メソッドと型クラス インスタンスを定義しました。

implicit def catchW[A](underlying: Catch[A]) = new CatchW(underlying)

class CatchW[A](underlying: Catch[A]) {
  def validation(body: => A): Validation[Throwable, A] = {
    underlying.withApply(_.fail)(body.success)
  }

  def vnel(body: => A): ValidationNEL[Throwable, A] = {
    underlying.withApply(_.failNel)(body.success)
  }
}

implicit def catchMonoid[A] = new Monoid[Catch[A]] {
  val zero = noCatch
  def append(x: Catch[A], y: => Catch[A]) = x or y
}
于 2012-02-15T12:59:52.443 に答える