🛡️

Scalaで学ぶアサーション入門:REPLで体感する安全なコードの書き方

に公開

はじめに

ソフトウェアを書いていると「ここは絶対にこうであるはずだ」という前提条件があります。
例えば、二つの要素を横に並べたときに「幅が一致していなければならない」といった状況です。

Scala には assert や ensuring といった仕組みがあり、その前提条件を明示してプログラムの誤りを早期に検出することができます。
今回は REPL を使いながら実際にアサーションを試してみましょう。

1. Scala REPLを起動する

ターミナルで以下を入力します:

scala

これで対話環境(REPL)が起動します。

2. assert の基本

まずはシンプルな assert の動作を確認します。

scala> val x = 5
val x: Int = 5

scala> val y = 10
val y: Int = 10

scala> assert(x < y) // 条件が true なので何も起きない

ここでは x < y が成り立つので問題なし。
では、条件が false になった場合を見てみましょう:

scala> assert(x > y)
java.lang.AssertionError: assertion failed
  at scala.Predef$.assert(Predef.scala:264)
  ... 34 elided

このように、想定外の状態に陥ると即座にエラーを出してくれます。

3. 説明文をつける

assert には失敗したときの説明文を追加できます。

scala> assert(x > y, "x should be greater than y")
java.lang.AssertionError: assertion failed: x should be greater than y
  at scala.Predef$.assert(Predef.scala:279)
  ... 34 elided

エラーメッセージが明示的になり、デバッグがしやすくなります。

4. 実用例 ― 要素の幅を比較する

書籍の例を簡略化して、二つの要素の「幅」が一致しているかを確認する関数を作ってみましょう。

scala> case class Element(width: Int, contents: String)
class Element

scala> def above(e1: Element, e2: Element): Element = {
     |   assert(e1.width == e2.width, "widths must match")
     |   Element(e1.width, e1.contents + "\n" + e2.contents)
     | }
def above(e1: Element, e2: Element): Element

動作確認:

scala> val e1 = Element(5, "Hello")
val e1: Element = Element(5,Hello)

scala> val e2 = Element(5, "World")
val e2: Element = Element(5,World)

scala> above(e1, e2)
val res4: Element =
Element(5,Hello
World)

幅が揃っているので正常に動作。
一方で不一致だとどうなるか:

scala> val e3 = Element(3, "Oops")
val e3: Element = Element(3,Oops)

scala> above(e1, e3)
java.lang.AssertionError: assertion failed: widths must match
  at scala.Predef$.assert(Predef.scala:279)
  at above(<console>:2)
  ... 34 elided

期待通りにエラーが出ました 🎉

5. ensuring を使ってみる

関数の戻り値に対して条件を課したい場合は ensuring が便利です。

例: 「計算した幅が必ず0以上であることを保証する」

scala> def safeWidth(w: Int):Int = 
     |   (w / 2) ensuring (_ >= 0)
def safeWidth(w: Int): Int

動作確認:

scala> safeWidth(10)
val res6: Int = 5

scala> safeWidth(-4)
java.lang.AssertionError: assertion failed
  at scala.Predef$.assert(Predef.scala:264)
  at scala.Predef$Ensuring$.ensuring$extension(Predef.scala:359)
  at safeWidth(<console>:2)
  ... 34 elided

返り値に直接アサーションを仕込めるので、コードがスッキリします。

Discussion