【Kotlin】runCatching の後をメソッドチェーンにしてコードを読みやすくする

## はじめに

こんなコードを見たことはないだろうか。

``` kotlin: MainViewModel.kt
class MainViewModel : ViewModel() {

    val TAG = "RUN-CATCHING-CHAIN"
    
    private fun badCase() {
        viewModelScope.launch {
            runCatching {
                randomThrows()
            }.onSuccess {
                runCatching {
                    randomThrows()
                }.onSuccess {
                    val result = runCatching {
                        randomThrows()
                    }
                    Log.d(TAG, "[bad case] result: ${result.getOrNull()}")
                }.onFailure {
                    Log.w(TAG, "onFailure: $it.message")
                }
            }.onFailure {
                Log.w(TAG, "onFailure: $it.message")
            }
        }
    }

    private fun randomThrows(): String {
        return if ((0..1).random() == 0) {
            throw Exception("Exception")
        } else {
            "R"
        }
    }
}
```

見ての通り、 `onSuccess` でどんどんネストしていくコードである。 `onSuccess` のラムダが１行だからいいものの、複数行でかつ `if` や `when` 等 があるとコードを追うのがかなり辛くなる。経験上、こういうコードにはかなり高い確率で既に不具合が存在しており、そして将来的に不具合が埋め込まれる。

では、ネストを解消した場合はどうだろう。

``` kotlin: MainViewModel.kt
    fun badCase2() {
        viewModelScope.launch {
            val result1 = runCatching {
                randomThrows()
            }
            if (result1.isFailure) {
                Log.w(TAG, "onFailure: ${result1.exceptionOrNull()?.message}")
                return@launch
            }

            val result2 = runCatching {
                Log.d(TAG, "result1:${result1.getOrNull()}")
                randomThrows()
            }

            if (result2.isFailure) {
                Log.w(TAG, "onFailure: ${result2.exceptionOrNull()?.message}")
                return@launch
            }

            val result3 = runCatching {
                Log.d(TAG, "result2:${result2.getOrNull()}")
                randomThrows()
            }

            if (result3.isSuccess) {
                Log.d(TAG, "result3:${result2.getOrNull()}")
            } else {
                Log.w(TAG, "onFailure: ${result3.exceptionOrNull()?.message}")
            }
        }
```

`Result` の中身をいちいち取り出すのが面倒。また、変数が多くあり、かつスコープが広いので扱う変数を間違ってしまいそうである。
変数の取り扱い間違いを解消するため、`let` 等のスコープ関数を使ってメソッドチェーンにしてみる。

``` kotlin: MainViewModel.kt
    fun badCase3() {
        viewModelScope.launch {
            runCatching {
                randomThrows()
            }.let { result1 ->
                if (result1.isFailure) {
                    Log.w(TAG, "onFailure: ${result1.exceptionOrNull()?.message}")
                    return@launch
                }
                runCatching {
                    Log.d(TAG, "result1:${result1.getOrNull()}")
                    randomThrows()
                }
            }.let { result2 ->
                if (result2.isFailure) {
                    Log.w(TAG, "onFailure: ${result2.exceptionOrNull()?.message}")
                    return@launch
                }
                runCatching {
                    Log.d(TAG, "result2:${result2.getOrNull()}")
                    randomThrows()
                }
            }.also { result3 ->
                if (result3.isSuccess) {
                    Log.d(TAG, "result3:${result3.getOrNull()}")
                } else {
                    Log.w(TAG, "onFailure: ${result3.exceptionOrNull()?.message}")
                }
            }
        }
    }
```

まだ、 `Result` の中身を取り出す面倒が残っており、またこれに限らずこれまでのサンプルコード全部に言えることだが、 `runCatching` するたびに成功・失敗の判定するのが面倒である。

今記事では [`mapCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/map-catching.html)、 [`recoverCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/recover-catching.html) を使ってメソッドチェーンを実現し、もっと読みやすいコードにしてみる。
[`mapCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/map-catching.html)、 [`recoverCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/recover-catching.html)を初めて見るという人は予め予習をしなくてもサンプルコードを見てもらえればどういう関数であるかわかるはずなのでご心配なく。

## `mapCatching` で処理を チェーン させる

一番シンプルなパターン。 ２回目以降の `runCatching` を `mapCatching` に置き換えて処理をチェーンする。

```kotlin: MainViewModel.kt
class MainViewModel : ViewModel() {

    val TAG = "RUN-CATCHING-CHAIN"

    fun case1() {
        viewModelScope.launch {
            val result = runCatching {
                "1"                                    // ①
            }.mapCatching {
                it + "2"                               // ②
            }.mapCatching {
                it + "3"                               // ③
            }.mapCatching {
                it + "4"                               // ④
            }

            Log.d(TAG, "[case1] result:${result.getOrNull()}") // ⑤
        }
    }
}

    // [case1] result:1234
```

## `mapCatching` で `Exception` が発生した場合は、 `onFailure` に入る

`mapCatching` で処理が失敗、つまり `Exception` が発生した場合に何か処理を実行したい場合は `onFailure` で入る。

```kotlin: MainViewModel.kt
    fun case2() {
        viewModelScope.launch {
            val result = runCatching {
                "1"                                      //①
            }.mapCatching {
                throw Exception("Exception")             //②
            }.onFailure {
                Log.w(TAG, "onFailure: $it.message")     //③
            }

            Log.d(TAG, "[case2] result: ${result.getOrNull()}")  //④
        }
    }

    // [case2] result: null
```    

`mapCatching` で `Exception` が発生しない場合は `onFailure` に入らないのでご心配なく。

```kotlin: MainViewModel.kt
    fun case3() {
        viewModelScope.launch {
            val result = runCatching {
                "1"                                      // ①
            }.mapCatching {
                it + "2"                                 // ②
            }.onFailure {
                Log.w(TAG, "onFailure: $it.message")
            }

            Log.d(TAG, "[case3] result: ${result.getOrNull()}")  // ③
        }
    }

    // [case3] result: 12
```    

`mapCatching` で `Exception` が発生した場合は、 以降の `mapCatching` をスキップして `onFailure` に入る。この動作は嬉しい。一連の処理の中で失敗処理を１度だけ実行したい場合は一番下に `onFailure` を配置すれば良い。

``` kotlin: MainViewModel.kt
    fun case4() {
        viewModelScope.launch {
            val result = runCatching {
                "1"                                     // ①
            }.mapCatching {
                //NOTE: 直接 throw Exception() を実行すると次の mapCatching の
                //      it がコンパイルエラーになるのでメソッド経由で Exception を発生させる
                throwException()                        // ②
            }.mapCatching {
                it + "3"
            }.onFailure {
                Log.w(TAG, "onFailure: $it.message")    // ③
            }

            Log.d(TAG, "[case4] result: ${result.getOrNull()}") // ④
        }
    }
            
    private fun throwException(): String {
        throw Exception("Exception")
    }

    //[case4] result: null
```

初っ端の runCatching で Exception が発生してしまっても、以降の mapCatching をスキップして `onFailure` に入るのでご心配なく。

``` kotlin: MainViewModel.kt
    fun case5() {
        viewModelScope.launch {
            val result = runCatching {                
                throwException()                        // ①
            }.mapCatching {
                it + "2"                               
            }.onFailure {
                Log.w(TAG, "onFailure: $it.message")    // ②
            }

            Log.d(TAG, "result: ${result.getOrNull()}") // ③
        }
    } 

    // [case5] result: null
```

`onSuccess` を使うとどうなるの？ という疑問が湧くだろうから、 チェーンの途中に `onSuccess` を配置した場合を検証してみる。結果、 `mapCatching` で正常しても次の `onSuccess` には入らない。正常処理をチェーンする場合はやっぱり `mapCatching` を使おう。

```kotlin:MainViewModel.kt
    fun case6() {
        viewModelScope.launch {
            val result = runCatching {
                "1"                                    // ①
            }.mapCatching {
                it + "2"                               // ②
            }.onSuccess {
                it + "3"
            }.onFailure {
                Log.w(TAG, "onFailure: ${it.message}")
            }

            Log.d(TAG, "[case6] result: ${result.getOrNull()}") // ③
        }
    }

    // [case6] result: 12
 ```   

## `recoverCatching` で失敗を成功に戻す

`mapCatching` で `Exception` が発生した場合でもチェーンを続行したい場合は `recoverCatching` を使う。 `recoverCatching` が正常であればチェーンを続行できる。 `recovery` という名の通り、失敗した場合の正常に戻す処理を書くと良いだろう。

 ```kotlin:MainViewModel.kt
     fun case7() {
        viewModelScope.launch {
            val result = runCatching {
                "1"                                  // ①
            }.mapCatching {
                throw Exception("Exception")         // ②
            }.recoverCatching {
                "3"                                  // ③
            }.mapCatching {
                it + "4"                             // ④
            }.onFailure {
                Log.w(TAG, "onFailure: $it.message")
            }

            Log.d(TAG, "[case7] result: ${result.getOrNull()}") // ⑤
        }
    }

    // [case7] result: 34
 ```

ただし、残念な点がある。それは、 `recoverCatching` は引数として`Thowabl`e を受け取るので、　これまでのチェーンで繋いで来た値がここで途切れてしまうことだ。
 
`mapCatching` で `Exception` が発生しない場合は、 `recoverCatching` に入らない。

```kotlin:MainViewModel.kt
    fun case8() {
        viewModelScope.launch {
            val result = runCatching {
                "1"                       // ①
            }.mapCatching {
                it + "2"                  // ②
            }.recoverCatching {
                "3"
            }.mapCatching {
                it + "4"                  // ③
            }

            Log.d(TAG, "[case8] result: ${result.getOrNull()}") // ④
        }
    }

    // [case8] result: 124
  ```

`recoverCatching` でさらに `Exception` が発生した場合は、以降の `mapCatching` をスキップして `onFailure` に入る。

```kotlin:MainViewModel.kt
    fun case9() {
        viewModelScope.launch {
            val result = runCatching {
                throw Exception("Exception")          // ①
            }.recoverCatching {
                throwException()                      // ②
            }.mapCatching {
                it + "3"
            }.onFailure {
                Log.w(TAG, "onFailure ${it.message}") // ③
            }

            Log.d(TAG, "[case9] result: ${result.getOrNull()}") // ④
        }
    }

    // [case9] result: null
```

`recoverCatching` でさらに `Exception` が発生した場合、 以降に `recoverCatching` が存在すれば `recoverCatching` に入る。

```kotlin:MainViewModel.kt
    fun case10() {
        viewModelScope.launch {
            val result = runCatching {
                "1"                          // ①
            }.mapCatching {
                throw Exception("Exception") // ②
            }.recoverCatching {
                throwException()             // ③
            }.mapCatching {
                it + "4"
            }.recoverCatching {
                "5"                          // ④
            }

            Log.d(TAG, "[case10] result: ${result.getOrNull()}") // ⑤
        }
    }

    // [case10] result: 5
```

## メソッドチェーンを途中で切りたい場合は `onFailure` を早めに配置する

`mapCatching` や `recoverCatching` によるメソッドチェーンの途中でチェーンを断ち切りたい時があるだろう。その場合は、 失敗が発生する場所に `onFailure` を配置する。

```kotlin:MainViewModel.kt     
    fun case11() {
        viewModelScope.launch {
            val result = runCatching {
                "1"                                    // ①
            }.mapCatching {
                throwException()                       // ②
            }.onFailure {
                Log.w(TAG, "onFailure: ${it.message}") // ③
            }.mapCatching {
                it + "4"
            }

            Log.d(TAG, "[case11] result: ${result.getOrNull()}") // ④
        }
    }
    
    // [case11] result: null
```    

##  `onSuccess` では `mapCatching` のようなメソッドチェーンができない

最後に `mapCatching` を使わなくても `onSuccess` でメソッドチェーンができるのでは？ という淡い期待を断ち切っておく。

```kotlin:MainViewModel.kt 
    fun case12() {
        viewModelScope.launch {
            val result = runCatching {
                "1"          // ①
            }.onSuccess {
                it + "2"     // ②
            }.onSuccess {
                it + "3"
            }

            Log.d(TAG, "[case12] result: ${result.getOrNull()}") // ③
        }
    }

    // [case12] result: 1
```

## 最後に

以下の点において読みやすいコードになったのではないだろうか。

* `Resull` の中身を取り出す面倒が軽減された
* 失敗はスルーできるので `runCatching` するたびに成功、失敗の判定の必要がない
* `recoverCatching` によって失敗ルートを成功ルートに戻すことが可能になった。失敗ルートの中に成功ルートを実装する必要が無くなった

私的には宣言的にコードを書くことによって、各メソッドのラムダの中をモジュール化によって１行程度に収めようとするマインドが働くはずで、そこが一番のメリットだと思っている。

```kotlin:MainViewModel.kt
    fun runChain() {
        viewModelScope.launch {
            val result = runCatching {
                doSomething1()
            }.mapCatching {
                doSomething2()
            }.recoverCatching {
                doSomething3()
            }.mapCatching {
                doSomething4()
            }.recoverCatching {
                doSomething5()
            }.mapCatching {
                doSomething6()
            }.onFailure {
                Log.w(TAG, "onFailure: ${it.message}") 
            }.mapCatching {
                doSomething7()
            }.onFailure {
                Log.w(TAG, "onFailure: ${it.message}")
            }

            Log.d(TAG, "[runChain] result: ${result.getOrNull()}")
        }
    }
```