Scalaでの例外処理メモ

例外についてものすごく参考になった記事と、個人的メモ。

参考になった記事

Scala界隈では有名ながくぞさんのSlideですね。私はこれを読んで初めて例外が何か理解できた気がしました。

他にもスライドはあります。

https://gakuzzzz.github.io/slides/

Javaにおける例外

Scalaにおける例外の立ち位置や、どう扱うべきかを理解するにはJavaで例外をどう扱うかを先に理解するのがわかりやすいです。

Javaの例外には3種類あります。

  • 致命的なエラー: Error
  • 非検査例外:RuntimeException
  • 検査例外:Exception

このうち、致命的な例外と非検査例外については捕捉せず、検査例外に対してのみ呼び出し側でハンドリングをします。

エラーの種類ハンドリング
致命的なエラー(ErrorNoOutOfMemoryError, StackOverflowError など。
非検査例外(RuntimeExceptionNoIllegalArgumentException など。バグ起因系。
検査例外(ExceptionYes業務例外など。
エラーの種類とハンドリングすべきかどうか

基本的に、検査例外以外はハンドリングする必要がありません。※ハンドリングすべきでない。

Scalaにおける例外

Scalaにおいては、検査例外をなくし、例外は全て致命的なエラーもしくは、非検査例外のハンドリングしてはならないもの、しなくて良いものだけになりました。

  • 致命的な例外
  • 非検査例外

のみ。

検査例外をどう扱うか?

Scalaで検査例外を扱う際は型で表現します。TryEither などですね。Option もそうです。この辺りの詳しい説明はドワンゴの新卒研修資料がわかりやすいと思います。

scala-text/エラー処理

これにより、型で利用者側が明確に例外処理をいけないんだなと分かりますし、Scalaのパターンマッチを使ってあげれば網羅性検査も可能です。

実装アイデア

実際にどういうふうに書くかの実装アイデアを考えます。

下記のようなシチュエーションを考えます。

インターネット上でプリペイド式の書籍を購入できるサービスを開発しています。

今現在、ある書籍(Book)を購入する処理を書いています。

この処理には次の業務例外が発生すると想定されています。

  • チャージされているお金が足りない場合
  • 倉庫に在庫が存在しない場合

成功時には、荷物追跡IDを返すことにします。

これをScalaで書く場合を考えます。

// ISBNコード
case class ISBN(value: String)
// 価格
case class Price(value: Int)
// 本
case class Book(isbn: ISBN, title: String, price: Price)
// 追跡番号
case class TrackingNumber(value: String)

trait BookPurchaseService { import BookPurchaseService._ // 購入処理 def purchase(book: Book): Either[PurchaseError,TrackingNumber] }

object BookPurchaseService { sealed trait PurchaseError // 在庫が足りない case object NoStock extends PurchaseError // チャージ金額が足りない case class InsufficientChargeAmount(insufficientAmount: Int) extends PurchaseError }

こんな感じにしてあげると、PurchaseErrorsealed trait になっているので、網羅性検査も言語機能側でやってくれて便利になるかなと思います。

書き方のイメージは自分の中では今のところ上記のような感じですが、ここで重要なのは

  • 例外には3種類あって、捕捉すべき例外と、捕捉すべきでない例外がある
  • 捕捉すべき例外はScalaでは値で表現する

の2点かと思います。