1. 序章

このチュートリアルでは、Scalaでの例外処理について説明します。 言語によって提供されるさまざまな構成を使用して、それらを処理するさまざまな方法を検討します。

2. 例外とは何ですか?

例外は、プログラムの通常のフローを変更するイベントです。

例外処理は、例外の発生に対応するメカニズムです。

例外は、チェックまたはオフにすることができます。 ただし、Scalaはチェックされていない例外のみを許可します。 これは、コンパイル時に、メソッドが処理していない例外をスローしているかどうかを知ることができないことを意味します。

3. 基本的な電卓

Scalaで例外を処理するさまざまな方法を示す簡単な計算機を書いてみましょう。 正の整数のみが追加されます。

object CalculatorExceptions {
  class IntOverflowException extends RuntimeException
  class NegativeNumberException extends RuntimeException
}

object Calculator {
  import CalculatorExceptions._

  def sum(a: Int, b: Int): Int = {
    if (a < 0 || b < 0) throw new NegativeNumberException
    val result = a + b
    if (result < 0) throw new IntOverflowException
    result
  }
}

このメソッドは、加数の1つが負の場合は NegativeNumberException をスローし、合計がIntの範囲をオーバーフローした場合はIntOverflowExceptionをスローします。

電卓を使用するときは、これらの例外を処理する方法を検討する必要があります。

次の段落では、Scalaでそれを行うさまざまな方法を見ていきます。

4. 試して/キャッチ/最後に

Scalaで例外を処理できる基本的な方法は、Javaのものと非常によく似たtry / catch/finally構文です。

次の例では、テストを簡単にするために、キャッチされた例外ごとに異なる負のエラーコードを返します。

def tryCatch(a: Int, b: Int): Int = {
  try {
    return Calculator.sum(a,b)
  } catch {
    case e: IntOverflowException => -1
    case e: NegativeNumberException => -2
  } finally {
    // This block will always be invoked
    println("Calculation done!")
  }
}

上記の例について注意すべき点がいくつかあります。

  • 「危険な」コードはtryブロックに入ります
  • finallyブロックのコードは、前に何が起こっても常に実行されます –このブロックは、たとえば、データベース接続などのリソースを確実に閉じる場合に便利です。
  • caseステートメントをcatch セクションで使用して、さまざまな例外タイプに一致させることができます

この構文では、例外がスローされるとすぐに[X10X]例外をどう処理するかを決定する必要があります。 この例では、キャッチした例外に応じて異なるエラーコードを返しています。

5. 試行/成功/失敗

Try [T]は代数的データ型であり、そのインスタンスは Success[T]およびFailure[T]です。

それを使用してtryCatchメソッドを書き直してみましょう。

def trySuccessFailure(a: Int, b: Int): Try[Int] = Try {
  Calculator.sum(a,b)
}

これで、いくつかのテストを使用して、trySuccessFailureの結果が機能スタイルでどのように使用されるかを示すことができます。

"trySuccessFailure" should "handle NegativeNumberException" in {
  import CalculatorExceptions._
  val result = trySuccessFailure(-1,-2)
  result match {
    case Failure(e) => assert(e.isInstanceOf[NegativeNumberException])
    case Success(_) => fail("Should fail!")
  }
}

it should "handle IntOverflowException" in {
  import CalculatorExceptions._
  val result = trySuccessFailure(Int.MaxValue,1)
  result match {
    case Failure(e) => assert(e.isInstanceOf[IntOverflowException])
    case Success(_) => fail("Should fail!")
  }
}

it should "return the correct sum" in {
  import CalculatorExceptions._
  val result = trySuccessFailure(3,2)
  result match {
    case Failure(e) => fail("Should succed!")
    case Success(result) => assert(result == 5)
  }
}

このメソッドは、SuccessまたはFailureクラスのいずれかを返します。 パターン一致を使用すると、関数の結果を簡単に処理できます。

6. オブジェクトをキャッチ

例外をキャッチする別の方法はscala.util.control.Exceptionオブジェクトから来ます。catchオブジェクトを使用してCalculator.sumを処理しましょう。

def catchObjects(a: Int, b: Int): Try[Int] = allCatch.withTry {
  Calculator.sum(a,b)
}

allCatch.withTryオブジェクトを使用すると、すべての例外をキャッチし、 Tryで処理できます。 上記のコードは、以前に実装したtrySuccessFailureとまったく同じように動作します。

scala.util.control.Exception は、すぐに使用できる opt どちらかを提供し、それぞれオプションで例外をラップします。 またはどちらか

catchオブジェクトの興味深い機能は、カスタムマッチャーを定義できることです。定義がいかに簡単かを確認するために、NegativeNumberExceptionのみを処理するものを記述できます。

val myCustomCatcher = catching(classOf[NegativeNumberException])

def customCatchObjects(a: Int, b: Int): Try[Int] = myCustomCatcher.withTry{
  Calculator.sum(a,b)
}

さらに、 scala.util.control.Exception.ignoring() catchオブジェクトを使用して、指定された例外をキャッチして無視することができます。

def ignoringAndSum(a: Int, b: Int) =
  ignoring(classOf[NegativeNumberException], classOf[IntOverflowException]) {
    println(s"Sum of $a and $b is equal to ${Calculator.sum(a, b)}")
  }

これにより、前述の例外を無視して、コードのパスブロックが実行されます。

いくつかのテストを使用して、新しいマッチャーの動作を示しましょう。

"customCatchObjects" should "handle NegativeNumberException" in {
  import CalculatorExceptions._
  val result = customCatchObjects(-1,-2)
  result match {
    case Failure(e) => assert(e.isInstanceOf[NegativeNumberException])
    case Success(_) => fail("Should fail!")
  }
}

it should "handle IntOverflowException" in {
  import CalculatorExceptions._
  assertThrows[IntOverflowException] {
    customCatchObjects(Int.MaxValue,1)
  }
}

it should "return the correct sum" in {
  import CalculatorExceptions._
  val result = customCatchObjects(3,2)
  result match {
    case Failure(e) => fail("Should succed!")
    case Success(result) => assert(result == 5)
  }
}
it should "ignore specified exceptions" in {
  Examples.ignoringAndSum(-1, -2)
}

ご覧のとおり、 IntOverflowException 例外がスローされた場合、それは処理されません。

キャッチオブジェクトは、例外処理ロジックを一元化し、コードの繰り返しを回避するのに便利です。

ただし、試行/成功/失敗と比較してあまり知られていません。

7. 機能的構成可能性

ここのところ、 例外の処理を後の段階に延期することができました。 これは、機能的な構成可能性を目指す場合に不可欠です。

メソッドを組み合わせて、チェーンの最後で例外の処理を実行する方法をより適切に示すことができます。

"customCatchObjects composed with trySuccessFailure" should "return the correct sum" in {
  import CalculatorExceptions._
  val result = customCatchObjects(3, 2) flatMap (trySuccessFailure(_, 3))
  result match {
    case Failure(e)      => fail("Should succed!")
    case Success(result) => assert(result == 8)
  }
}

it should "print an error" in {
  import CalculatorExceptions._
  val result = customCatchObjects(-1, -2) flatMap (trySuccessFailure(_, 3))
  result match {
    case Failure(e)      => println("Found error!")
    case Success(result) => fail("Should fail!")
  }
}

上記のコードでは、最後に例外をどう処理するかを決定します。 trySuccessFailurecustomCatchObjectsを作成するとき、考える必要はありませんでした。それについて。

Try / Success / Failureオブジェクトとcatchオブジェクトの両方は、機能的な構成可能なコードの記述に役立ちます。 それらは、古典的なtry / catch によって強制されるようにすぐにそれらを処理する代わりに、例外の評価を延期することを可能にします。

8. 結論

この記事では、Scalaの例外処理のサポートについて説明しました。

キャッチオブジェクトと試行/成功/失敗のどちらかを選択する場合、 tトレードオフはコードのアクセス可能性とコードの再利用性の間です。 Scalaでは、機能的な構成可能性を実現するためのより簡単な方法を提供するため、 try / catch /finallyよりも明らかに好ましいです。

いつものように、コードはGitHubから入手できます。