ScalaでThrowableを継承したobjectってどうなっちゃうんだろう

# はじめに

scalaにはobjectなるシングルトンな値を定義できます。 *1
下のようにThrowableを継承させる定義もできます。

```scala
object ObjectThrowable extends Throwable
```

意気揚々と下のように


```scala
sealed abstract class UserError extends Throwable

object NameFormatError extends UserError
object InvalidAgeError extends UserError
```

なんて定義をしてましたが、objectでThrowableを定義するのはよくないな。という結論になりました。

なぜそんなことをしたの？ といいますと`object NameFormatError`にはフィールドがないし、classにする価値なさそう。という発想でした。

これがよくなかったわけです。

# 何が起きるのか

```scala
object ObjectThrowable extends Throwable // 1行目
object ThrowableTest extends App {
  def testObjectThrow() = {
    try {
      throw ObjectThrowable // 5行目
    } catch {
      case t: Throwable => t.printStackTrace()
    }

    try {
      throw ObjectThrowable / /11行目
    } catch {
      case t: Throwable => t.printStackTrace()
    }
  }

  testObjectThrow()
}
```

これを実行すると以下のようにコンソールに出てきます。

```
ObjectThrowable$
	at ObjectThrowable$.<clinit>(ThrowableTest.scala:1)
	at ThrowableTest$.testObjectThrow(ThrowableTest.scala:5)
	at ThrowableTest$.delayedEndpoint$ThrowableTest$1(ThrowableTest.scala:17)
	at ThrowableTest$delayedInit$body.apply(ThrowableTest.scala:2)
~~~略~~~
ObjectThrowable$
	at ObjectThrowable$.<clinit>(ThrowableTest.scala:1)
	at ThrowableTest$.testObjectThrow(ThrowableTest.scala:5)
	at ThrowableTest$.delayedEndpoint$ThrowableTest$1(ThrowableTest.scala:17)
	at ThrowableTest$delayedInit$body.apply(ThrowableTest.scala:2)
~~~略~~~
```

二度objectをthrowして`printStackTrace`を実行しているので、スタックトレースが二度出ていていますがスタックトレースの内容が妙ですね。

二度目の`printStackTrace`を呼び出されているオブジェクトは11行目でthrowされているので、本来下記のようなスタックトレースが出て欲しいですよね。

```
ObjectThrowable$
    at ObjectThrowable$.<clinit>(ThrowableTest.scala:1)
    at ThrowableTest$.testObjectThrow(ThrowableTest.scala:11)
```

下記のようなコードで試してみるとわかりやすく、

```scala
object ObjectThrowable extends Throwable
object ThrowableTest extends App {
  def testObjectThrow() = {
    ObjectThrowable // 4行目

    try {
      throw ObjectThrowable
    } catch {
      case t: Throwable => t.printStackTrace()
    }
  }

  testObjectThrow()
}
```

4行目でobjectが最初に評価された場所がスタックトレースに記録されます。

```
ObjectThrowable$
	at ObjectThrowable$.<clinit>(ThrowableTest.scala:1)
	at ThrowableTest$.testObjectThrow(ThrowableTest.scala:4)
	at ThrowableTest$.delayedEndpoint$ThrowableTest$1(ThrowableTest.scala:19)
	at ThrowableTest$delayedInit$body.apply(ThrowableTest.scala:2)
```

これはあたり前の挙動で、Throwableは生成時にスタックトレースを埋めるからです。

```java
class Throwable {
    // from https://github.com/AdoptOpenJDK/openjdk-jdk/blob/master/src/java.base/share/classes/java/lang/Throwable.java#L255-L257
    public Throwable() {
        fillInStackTrace();
    }

    public synchronized Throwable fillInStackTrace() {
        // from https://github.com/AdoptOpenJDK/openjdk-jdk/blob/master/src/java.base/share/classes/java/lang/Throwable.java#L795-L802
        if (stackTrace != null ||
            backtrace != null /* Out of protocol state */ ) {
            fillInStackTrace(0);
            stackTrace = UNASSIGNED_STACK;
        }
        return this;
    }
}
```

objectが評価されるタイミングで、親クラスにあたるThrowableのコンストラクタも呼ばれてスタックトレースが埋められます。

# ということで

objectでThrowableを継承するのは混乱を生むかもしれませんね。
意図的にスタックトレースをobjectを最初に評価した場所に固定したい場合以外は、
そもそも、Throwableは生成するタイミングのスタックトレースを自身の状態として保持する。
という事情を考えるとobjectではなくclassで定義したほうが良い。という考え方をした方がいいですね。


*1 厳密にはシングルトンではないようです