【Kotlin】try-catch がやりにくければ runCatching を使ってみよう！

![Kotlin 1.7](https://img.shields.io/badge/Kotlin-1.7-blue)

例外がスローされたらあれを実行したいが、
スローされなかったらこれを実行したい。
そういうときありますよね。

## `try`-`catch`（-`finally`）では書きにくいときがある

`try`-`catch`（-`finally`）では、
例外がスローされたときの処理（とスローされてもされなくてもする処理）は書けても、
スローされなかったときの処理は単純には書けません。

例で考えてみましょう。

まず、例外がスローされることを考えなくてよい場合を考えます。

```Kotlin
fun noException() {
    val myValue =
            getMyValue() // 値を取得する。
                    .also {
                        onMySuccess(it) // その後 onMySuccess を呼び出す。
                        onMyFinally() // 最後に onMyFinally を呼び出す。
                    }

    setMyValue(myValue)
}
```

ここで `getMyValue()` が例外をスローする可能性があるとしましょう。
例外がスローされたときには `onMyFailure` 関数を呼び出して代わりの値を取得するようしましょう。
簡単に考えるとこうなります。

```Kotlin
fun useTryCatch1() {
    val myValue =
            try {
                getMyValue() // getMyValue() は例外をスローする可能性がある。
                        .also { onMySuccess(it) } // 例外がスローされなかったら onMySuccess を呼び出す。
            } catch (e: MyException) {
                onMyFailure(e) // 例外がスローされたら onMyFailure を呼び出し、代わりの値を取得する。
            } finally {
                onMyFinally() // 最後に onMyFinally を呼び出す。
            }

    setMyValue(myValue)
}
```

ですがこれはあまりよくありません。`try` の中に関係ない `onMySuccess` 関数呼び出しも入ってしまっています。
もし不具合により誤って `onMySuccess` が例外をスローしてしまったときに、それが握りつぶされてしまい、その不具合の発見が遅れてしまいます。

`onMySuccess` を `try` ブロックの外に出しましょう。

```Kotlin
fun useTryCatch2() {
    val myValue =
            run {
                try {
                    getMyValue()
                } catch (e: Throwable) {
                    return@run onMyFailure(e)
                }.also {
                    onMySuccess(it)
                }
            }.also {
                onMyFinally()
            }

    setMyValue(myValue)
}
```

…うーん、複雑でわかりにくくなってしまいました。

## [`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数を使ってみよう

では `try`-`catch` の代わりに [`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数を使ってみましょう。

```Kotlin
fun useRunCatching() {
    val myValue =
            runCatching {
                getMyValue()
            }.fold(
                    onSuccess = { it.also { onMySuccess(it) } },
                    onFailure = { onMyFailure(it) }
            ).also {
                onMyFinally()
            }

    setMyValue(myValue)
}
```

シンプル！
`try`-`catch`-`finally` に、例外がスローされなかったときの処理ブロックが追加されたような、わかりやすい構造ですね！

## [`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) クラス — [`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数の返値型

[`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数は、与えられたブロックを実行し、 [`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) 型のオブジェクトを返します。

```Kotlin
val result: Result<MyValue> = runCatching { MyValue() }
```

もしブロック内で例外がスローされても、[`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数はその例外を外にスローしません。

ブロックを実行した結果の返値や例外は、[`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数が返した [`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) オブジェクトから取得することができます。

### [`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) クラスのメンバ

#### プロパティ [`isSuccess`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/is-success.html) と [`isFailure`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/is-failure.html)

ブロックの実行に成功した（ブロック内で例外がスローされなかった）かどうかは [`isSuccess`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/is-success.html) プロパティから知ることができます。
逆にブロックの実行に失敗した（例外がスローされた）かどうかは [`isFailure`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/is-failure.html) プロパティから知ることができます。

```Kotlin
if (result.isSuccess) { println("成功") }
if (result.isFailure) { println("失敗") }
```

#### 関数 [`getOrNull`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/get-or-null.html) と [`exceptionOrNull`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/exception-or-null.html)

ブロックを実行した結果の返値を得るには [`getOrNull`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/get-or-null.html) 関数を使います。
ブロック内で例外がスローされた場合は `null` が返ります。

ブロック内でスローされた例外を取得するには [`exceptionOrNull`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/exception-or-null.html) 関数を使います。
スローされなかった場合は `null` が返ります。

```Kotlin
result.getOrNull()
        ?.also { println("返値は $it です。") }
        ?: println("例外がスローされました。")
result.exceptionOrNull()
        ?.also { println("例外は $it です。") }
        ?: println("値が返されました。")
```

### [`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) クラスの拡張関数

[`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) クラスのメンバは先ほど述べた4つ（と[`toString`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/to-string.html)）だけです。
ですが拡張関数が多数用意されており、これらがとても便利です。

#### `get` 系関数

`get` 系関数は、ブロックの実行に成功したときにはその結果の返値を返します。
失敗したときの動作はそれぞれ異なります。

[`getOrThrow`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/get-or-throw.html) 関数：
　ブロック内でスローされた例外をそのままスローします。
[`getOrDefault`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/get-or-default.html) 関数：
　指定されたデフォルト値を返します。
[`getOrElse`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/get-or-else.html) 関数：
　指定された処理を実行します。

```Kotlin
val result = runCatching { "".toInt() }
try {
    result.getOrThrow()
} catch (e: Exception) {
    println("0") // > 0
}
result.getOrDefault(1).let { println(it) } // > 1
result.getOrElse { println(2) } // > 2
```

#### `on` 系関数

[`onSuccess`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/on-success.html) 関数のブロックは、[`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数のブロックの実行が成功したときだけ実行されます。
[`onFailure`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/on-failure.html) 関数のブロックは、[`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数のブロックの実行が失敗したときだけ実行されます。

これらはレシーバーの [`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) オブジェクトをそのまま返すので、チェイン呼び出しできます。

```Kotlin
result
        .onSuccess { onMySuccess(it) }
        .onFailure { onMyFailure(it) }
```

これらはインライン関数なので、ブロック内から `return` したり `throw` したりできます。

```Kotlin
fun myFun() {
    run {
        result
                .onSuccess { return@run } // 成功したら run ブロックから抜ける
                .onFailure { throw Exception() } // 失敗したら myFun 関数から例外をスローする
    }
}
```

#### `map` 系関数と [`recover`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/recover.html) 系関数

ここでは次のように例外クラスと関数が定義されているとします。

```Kotlin
class MyIntException : Exception()
class MyDoubleException : Exception()

fun throwMyIntException(): Int = throw MyIntException()
fun throwMyDoubleException(): Double = throw MyDoubleException()
```

##### `map` 系関数

`map` 関数と [`mapCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/map-catching.html) 関数は [`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数のブロックを実行した結果の返値を任意の型の別の値に変換します。

```Kotlin
runCatching { 3.14 }
        .map { it.toInt() }
        .onSuccess { println(it) } // > 3
runCatching { 3.14 }
        .mapCatching { it.toInt() }
        .onSuccess { println(it) } // > 3
```

ただし、変換処理中に例外がスローされたときの動作が異なります。
`map` 関数ではその例外を外にスローします。
[`mapCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/map-catching.html) では同関数が返す [`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) が失敗になり、その失敗原因がその例外になります。

```Kotlin
try {
    runCatching { 3.14 }
            .map { throwMyIntException() }
            .onSuccess { println(it) } // このラムダ式は実行されない。
            .onFailure { println(it) } // このラムダ式は実行されない。
} catch (e: MyIntException) {
    println(e) // > MyIntException
}

runCatching { 3.14 }
        .mapCatching { throwMyIntException() }
        .onFailure { println(it) } // > MyIntException
```

##### [`recover`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/recover.html) 系関数

関数 [`recover`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/recover.html) と [`recoverCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/recover-catching.html) は [`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数のブロックの実行が失敗したときにそれを成功に変えます。

```Kotlin
runCatching { throwMyDoubleException() }
        .recover { 3 }
        .onSuccess { println(it) } // > 3
runCatching { throwMyDoubleException() }
        .recoverCatching { 3 }
        .onSuccess { println(it) } // > 3
```

ただし、変換中に例外がスローされたときの動作が異なります。
[`recover`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/recover.html) 関数はその例外を外にスローします。
[`recoverCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/recover-catching.html) 関数では同関数が返す [`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) が失敗になり、その失敗原因がその例外になります。

```Kotlin
try {
    runCatching { throwMyDoubleException() }
            .recover { throwMyIntException() }
            .onSuccess { println(it) } // このラムダ式は実行されない。
            .onFailure { println(it) } // このラムダ式は実行されない。
} catch (e: MyIntException) {
    println(e) // > MyIntException
}

runCatching { throwMyDoubleException() }
        .recoverCatching { throwMyIntException() }
        .onFailure { println(it) } // > MyIntException
```

#### `fold` 関数

`fold` 関数は [`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数のブロックの実行が成功したときには引数 `onSuccess` で与えられた処理を行い、失敗したときには引数 `onFailure` で与えられた処理を行います。

先ほど紹介した関数 [`onSuccess`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/on-success.html) と [`onFailure`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/on-failure.html) を両方呼び出すのと似ていますが、1つ大きな違いがあります。
それは返値です。

`fold` 関数では、成功したときの処理と失敗したときの処理がともに同じ型の返値を持つ必要があり、それが `fold` 関数の返値になります。

```Kotlin
val double = runCatching { "".toInt() }.fold(
        onSuccess = { it.toDouble() },
        onFailure = { Double.NaN },
)
```

`try`-`catch` 式で返値を使う場合の代替にはこれを使うことになるでしょう。

## 注意

### 【〜Kotlin 1.4】[`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) は関数の返値には使えない

Kotlin 1.4 まででは、[`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) は関数の返値には使えません。

```Kotlin
fun getResult(): Result<Int> { // コンパイルエラー！
    return runCatching { 1 }
}
```

Kotlin 1.5 からは使えるようになりました。

https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md#limitations-legacy

## まとめ

`try`-`catch` の代替となる [`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) 関数と、その返値となる [`Result`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/index.html) クラスについて説明しました。