🛡️
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