【Kotlin】エラーを呼び出し元に伝えるさまざまな方法

###### お詫び

「【Kotlin】よりよい値の受け渡し（関数の引数と返値の設計）」というタイトルを予告しておりましたが、
エラーの話だけで結構な規模になってしまったため、
今回はそこだけの記事といたしました。

「【Kotlin】よりよい値の受け渡し（関数の引数と返値の設計）」につきましては
また後日公開いたします。

---

エラーが起きたときにそれを呼び出し元に伝える方法はいくつもあります。

## 🔘例外をスローする

「エラー」といえばまず例外を思い付くでしょう。

```Kotlin
@Throws(MyException::class)
fun myFun() {
    // ...
    
    if (isMyError) {
        throw MyException()
    }
    
    // ...
}

fun main() {
    try {
        myFun()
    } catch (e: MyException) {
        // エラー時の処理
        // ...
    }
}
```

### ⭕️長所：エラーの詳細を伝えられる

例外を使えば、例外の型やプロパティの値によって、
（単にエラーが起きたということだけでなく）
エラーの詳細な内容を呼び出し元に伝えることができます。

例えばローカルファイルへの書き込みに失敗したという場合でも、
原因は ディレクトリがない／書き込み権がない／ファイルがロックされている などさまざまです。
これを呼び出し元に伝えられるので、呼び出し元は原因に応じた対応を行うことができます。

### ❌短所：呼び出し元にエラー処理を強制できない

Kotlin では、関数の呼び出し元に例外処理を強制することができません[^例外処理を強制]。
そのため例外をスローする関数だということに気付かず、適切なエラー処理を行わなかった結果、実行時に例外がスローされてアプリがクラッシュするということが起こり得ます。

[^例外処理を強制]: Java には「検査例外」（checked exception）という仕組みがあり、 `Exception` クラス派生であり かつ `RuntimeException` クラス派生でない例外（バグでなくても普通に起こり得るエラーを表す）をスローする関数については、呼び出し元でその例外を処理するコードを書かないとコンパイルエラーになります。

### ❌短所：エラー処理が型安全でない

関数に例外を追加した際に、呼び出し元の修正を忘れてもコンパイルエラーにはならず、修正忘れに気付けません。

```Kotlin
try {
    myFun()
} catch (e: MyException1){
    // エラー時の処理
    // ...
} // MyException2 に対する例外処理を忘れている
```

### ❌短所：例外オブジェクトを生成する計算コストが大きい

例外オブジェクトを生成する際にはスタックトレースが生成されます。
これは計算コストが大きい処理です。
そのため短時間に高回数使用されエラーになることが多い関数では他の方法を採った方がよいことがあります。

### 💡使い所

呼び出し元にエラー処理を強制できないので、
エラー処理をしなくてよい場合、
つまり起こったら復帰のしようがないエラーを表すのに使うのがよいのではないでしょうか。

とはいえ Kotlin/JVM では Java の標準ライブラリーが、 `IOException` などの、
呼び出し元でエラー処理をすることを前提とした例外をスローしてくるのですが…。


## 🔘`null` を返す

成功時の返値が non-null であれば、
返値型を nullable にして
エラー時には `null` を返すという方法があります。

```Kotlin
fun myFun(): MyResult? {
    // ...
    
    if (isMyError) {
        return null
    }
    
    // ...
    
    return MyResult()
}
```


### ⭕️長所：呼び出し元にエラー処理を強制できる

呼び出し元で成功時の返値（non-null）を取得するためには、
まず返り値が `null` でない（エラーでない）ことを確認する必要があります。
そのため `null` であった（エラーであった）場合に対する処理が必要であることにも気付くことができます。

```Kotlin
fun main() {
    val myResult = myFun()
    if (myResult == null) {
        // エラー時の処理
        // ...
    } else {
        // 成功時の処理
        // ここでは myResult は non-null。
        // ...
    }
}
```

ただし、呼び出し元が成功時に返値を使用しない場合には強制力がありません。

Android の場合は、関数に `@CheckResult` アノテーションをつけることで、呼び出し元で返値を使用していなければ警告が出るようにできます。

### ❌短所：エラーの詳細を伝えられない

エラーの詳細を伝えられないので、
呼び出し元でエラーの原因によって処理を分岐したい場合には使えません。

### ❌短所：【Kotlin/JVM】nullable とパフォーマンス

JVM 上では、Kotlin の次の型は Java のプリミティブ型で表されます。
- `Double`
- `Float`
- `Long` / `ULong`
- `Int` / `UInt`
- `Short` / `UShort`
- `Byte` / `UByte`
- `Char`
- `Boolean`

しかしこれらを nullable にした型（`Int?` 型など）はボックス化されたラッパークラス（`java.lang.Integer` クラスなど）で表されます。

そのため non-null の場合と比べて、ボックス化／アンボックス化、ヒープ領域の確保、ガベッジコレクションなどの実行コストがかかります。
短時間に高回数使用される関数の場合にはパフォーマンスに有意な影響が出るかもしれません。

ただしこれは、プリミティブ型と比べるとコストが大きいというだけで、
non-null な参照型の場合と同じパフォーマンスです。

### 💡使い所

成功時の返値が non-null であり、
呼び出し元でエラーの種類を気にすることがない場合におすすめです。


## 🔘`Result` を返す

[`Result<T>` クラス](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/)のインスタンスは、型パラメータで指定された型のオブジェクトか任意の例外オブジェクトを持ちます。
これを返値型とする[^Resultを返値にする]ことで、エラー時に例外を（スローするのではなく）返値として返す方法です。

[^Resultを返値にする]: `Result` クラスは Kotlin 1.3 から stable になりましたが、Kotlin 1.3 および 1.4 では返値にすることができません。

```Kotlin
fun myFun(): Result<MyResult> {
    //...
    
    if (isMyError) {
        return Result.failure(MyException())
    }
    
    // ...
    
    return Result.success(myResult)
}

fun main() {
  myFun()
      .onSuccess { myResult ->
          // 成功時の処理
          // ...
      }
      .onFailure { e ->
          // エラー時の処理
          // ...
      }
}
```

### ⭕️長所：呼び出し元にエラー処理を強制できる

呼び出し元で成功時の結果を取得するためには、成功したかどうかを判定する処理を書く必要があります。

```Kotlin
myFun()
    .onSuccess { myResult -> 
        // 成功時の処理
        // ...
    }
```

そのため、エラー処理を失念するということがありません。

ただし「🔘`null` を返す」と同様、呼び出し元が成功時に返値を使用しない場合には強制力がありません。

### ❌短所：エラー処理が型安全でない

成功時の結果の型は型パラメータで指定しますが、
エラー時の例外の型は指定できません（`Throwable` です）。

そのため、例外の型に応じて呼び出し元の処理を分ける場合、
発生しないはずの例外については無視するか握りつぶすことになります。
関数が返す例外の種類を増やした際に、呼び出し元を修正するのをわすれていてもコンパイルエラーにならず、実行時エラーになったり動作が不正になったりすることがありえます。

```Kotlin
myFun()
    .onFailure { e ->
        when (e) {
            is MyException -> {
                // ...
            }
            else -> {
                // ありえないはず
            }
        }
    }
```

### ❌短所：例外オブジェクトを生成する計算コストが大きい

例外をスローする場合と同じく、例外オブジェクトを生成する必要があるため、スタックトレースの生成コストが掛かります。

### 💡使い所

呼び出し元にエラー処理を強制し、かつエラーの詳細を伝えたい場合によいでしょう。


## 🔘`sealed interface` を返す

返値型とする型を `sealed interface` で定義し、
そのサブタイプとして、成功時に返すクラスとエラー時に返すクラスを定義します。
エラー時に返すクラスはエラーの種類に合わせて複数あってもかまいません。

通常、成功時の返値とエラー時の返値に共通のメンバはないはずなので `sealed interface` を使いますが、
必要であれば `sealed class` を使っても違いはありません。

```Kotlin
sealed interface MyResult {
    class Success(
        val mySucceededResult: MySucceededResult
    ): MyResult
    
    class Failure1(
        val error: MyErrorInfo1
    ): MyResult
    
    class Failure2(
        val error: MyErrorInfo2
    ): MyResult
}

fun myFun(): MyResult {
    // ...
    
    if (myErrorInfo1 != null) {
        return MyResult.Failure1(myErrorInfo1)
    }
    
    if (myErrorInfo2 != null)
        return MyResult.Failure2(myErrorInfo2)
    }

    // ...
    
    return MyResult.Success(mySucceededResult)
}

fun main() {
    val myResult = myFun()
    when (myResult) {
        is MyResult.Success ->
            // 成功時の処理
            // ...
        is MyResult.Failure1 ->
            // エラー1の場合の処理
            // ...
        is MyResult.Failure2 ->
            // エラー2の場合の処理
            // ...
    }
}
```

### ⭕️長所：呼び出し元にエラー処理を強制できる

成功時の結果を取得するためには、まず返値が成功時の結果を表す型であることを確認する必要があります。
そのためエラー時の処理を失念することがありません。

ただし「🔘`null` を返す」と同様、呼び出し元が成功時に返値を使用しない場合には強制力がありません。

### ⭕️長所：エラー処理が型安全

エラーの種類ごとに `sealed interface` のサブクラスを実装すれば、
呼び出し元で `when` を使うことでそれらに対する処理を網羅できます。
漏れがあればコンパイルエラー[^漏れがあればコンパイルエラー]になりますので、返す例外が増えても修正を忘れることはありません。

[^漏れがあればコンパイルエラー]: `when` **文**の場合、Kotlin 1.7 以上ではコンパイルエラー、1.6 では警告、1.5 以下では警告もありません。`when` **式**（`when` の返値を使う）であれば、いずれのバージョンでもコンパイルエラーになります。

```Kotlin
fun main() {
    val myResult = myFun()
    when (myResult) {
        is MyResult.Success ->
            // 成功時の処理
            // ...
        is MyResult.Failure1 ->
            // エラー1の場合の処理
            // ...
        // エラー2の場合の処理を忘れている
    } // コンパイルエラー！　エラー2の場合の処理がないため
}
```

### ❌短所：関数ごとにクラスを実装する必要がある

ほぼその関数でしか使用できないクラスを実装する必要があります。

### 💡使い所

呼び出し元でエラーの種類により処理を変える必要がある、重要な関数で使うとよいでしょう。


## 🔘コールバックする

エラー時に呼び出されるコールバック関数を引数で受け取る方法です。

```Kotlin
fun myFun(onError: (MyErrorInfo) -> Unit) {
    // ...

    if (isMyError) {
        onError(MyErrorInfo())
        return
    }

    // ...
}

fun main() {
    myFun { e ->
        // エラー時の処理
        // ...
    }
}
```

### ⭕️長所：呼び出し元にエラー処理を強制できる

引数なので、（関数の定義でデフォルト引数を設定しない限りは）呼び出し元で必ず指定しなければなりません。

### ⭕️長所：エラー処理が型安全

エラーの種類ごとにコールバック関数を分けてそれぞれの引数の型を適切に実装すれば、エラーを型安全に処理することができます。
エラーの種類が増えた際には、呼び出し元を修正しないとコンパイルエラーになるので、修正忘れがありません。

ただし、種類が増えると引数も増え、呼び出し元の実装が面倒になります。

```Kotlin
fun myFun(
    onError1: (MyErrorInfo1) -> Unit,
    onError2: (MyErrorInfo2) -> Unit,
) {
    // ...
    
    if (isMyError1) {
        onError1(MyErrorInfo1())
        return
    }
    
    if (isMyError2) {
        onError2(MyErrorInfo2())
        return
    }
    
    // ...
}

fun main() {
    myFun(
        onError1: { e ->
            // エラー1の場合の処理
            // ...
        },
        onError2: { e ->
            // エラー2の場合の処理
            // ...
        },
    )
}
```

### ❌短所：エラー処理で `return` できない

このような関数は通常はインライン関数ではないため、
エラーだったら `return` するということができません。

```Kotlin
fun main() {
    myFunc { e -> 
        return // コンパイルエラー！
    }
}
```

### ❌短所：コールバック関数内での処理の結果を外に渡しにくい

コールバック関数は呼ばれるかどうかが分からないので、
その内で処理した結果を外に渡そうとすると、
受け取る変数を `var` にして、
デフォルト値（大抵は `null`）で初期化しておく必要があります。

```Kotlin
fun main() {
    var callbackResult: MyCallbackResult = null // var かつ nullable などにするしかない。
    myFunc { e ->
        // エラー時の処理
        // ...
        callbackResult = // ...
    }
}
```

### ❌短所：エラー時の返値

成功時に値を返す必要がある場合、
エラー時に何を返すか考える必要があります。

次のような方法があります。

#### エラー時の返値を引数で指定させる

エラー時の返値を引数で指定させる方法です。

```Kotlin
fun myFun(
    default: MyResult,
    onFailure: (MyErrorInfo) -> Unit,
): MyResult {
    // ...
    
    if (isMyError) {
        // エラーの場合は、コールバック関数を実行し、
        // 引数で指定されたデフォルト値を返す。
        onFailure(MyErrorInfo())
        return default
    }
    
    // ...
    
    return myResult
}

fun main() {
    val myResult = myFun(MyResult()) { e ->
        // エラー時の処理
        // ...
    }
}
```

#### コールバック関数の返値を返す

コールバック関数の返値型を成功時の返値と同じにして、
エラー時にはそれを元関数の返値にする方法です。

```Kotlin
fun myFun(onFailure: (MyErrorInfo) -> MyResult): MyResult {
    // ...
    
    if (isMyError) {
        // エラーの場合は、コールバック関数を実行し、
        // その返値を自身の返値とする。
        return onFailure(MyErrorInfo())
    }
    
    // ...
    
    return myResult
}

fun main() {
    val myResult = myFun { e ->
        // エラー時の処理
        // ...
    }
}
```

#### 成功時の結果もコールバックで返す

成功時の結果を、関数の返値ではなく、コールバックで返す方法です。

```Kotlin
fun myFun(
    onSuccess: (MyResult) -> Unit,
    onFailure: (MyErrorInfo) -> Unit,
) { // 関数の返値型は Unit にする。
    // ...
    
    if (isMyError) {
        // エラーの場合も、コールバック関数を実行する。
        onFailure(MyErrorInfo())
    }
    
    // ...
    
    // 成功の場合も、コールバック関数を実行する。
    onSuccess(myResult)
}

fun main() {
    myFun(
        onSuccess: { result ->
            // 成功時の処理
            // ...
        },
        onFailure: { e ->
            // エラー時の処理
            // ...
        },
    )
}
```

### 💡使い所

絶対に呼び出し元にエラー処理をさせたい、という場合にはこの方法しかなさそうです。


## 🚫アンチパターン：成功時の返値の型の範囲内で、特定の値を返す

次のような方法は避けるべきです。
- 成功時には負でない数値を返す関数で、  
  エラー時には負の値を返す。
- 成功時には空でない文字列やコレクションなどを返す関数で、  
  エラー時には空を返す。

Kotlin 標準ライブラリーの [`indexOf` 関数](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/index-of.html)などでも使われていますが、
Java のコレクションフレームワークとの互換のためでしょう。

### ❌短所：呼び出し元にエラー処理を強制できない

言うまでもなく、呼び出し元にエラー処理を強制できません。

### ❌短所：ドキュメントを読まないと、どうなったらエラーなのかわからない

関数のドキュメントをちゃんと読まなければ、
返値がどうだったらエラーなのかが分かりません。
それ以前に、エラーを返値で表すということすら分かりません。
関数のドキュメントが長ければ、読んでも見落とすかもしれません。

### ➡代替手段

代わりに「🔘`null` を返す」を使いましょう。


## 🚫アンチパターン：成功時の結果と例外の `Pair` を返す

関数の返値型を `Pair` にして、
成功時には一方に値をセットし、
エラー時には他方に値をセットする、
というような方法は使わないでください。

```Kotlin:🚫アンチパターン
fun myFunc(): Pair<MyResult?, MyException?> {
    // ...
    
    if (isMyError) {
        // エラー時
        return null to MyException()
    }

    // ...
    
    // 成功時
    return myResult to null
}
```

### ❌短所：両方が `null` の場合を考える必要がある

成功時の結果も例外も共に `null` になってしまった場合の処理を呼び出し元で書かないとコンパイルエラーになる場合があります。

```Kotlin
fun main() {
    val (myResult, myException) = myFun()
    myResult?.also {
        // 成功時の処理
        // ...
        return
    }
    myException?.also {
        // エラー時の処理
        // ...
        return
    }
    
    // ありえないはずだが、書かないとコンパイルエラーになる。
    throw RuntimeException("Pair(null, null) が返されました)
}
```

### ➡代替手段

代わりに「🔘`Result` を返す」を使いましょう。


## まとめ

まとめるとこうなります。

|	方法 |	エラー処理の強制 |	エラーの詳細 |	エラー処理の型安全 |	その他 |
|	---|	---|	---|	---|	---|
|	例外をスローする |	× |	○ |	× |	|
|	`null` を返す |	△ |	× |	− |	|
|	`Result` を返す |	△ |	○ |	× |	|
|	`sealed interface` を返す |	△ |	○ |	○ |	関数ごとにクラスを実装する必要がある |
|	コールバックする |	○ |	○ |	○ |	呼び出し元は実装しにくい |

△：呼び出し元が返値を使用する場合のみ強制できます。

万能の方法はなさそうです。

各方法の長所短所を考慮して、
実装する関数に適した方法を採用しましょう。

／以上