β€’
7 min read

Kotlin Result의 onFailureμ—μ„œ μ˜ˆμ™Έλ₯Ό λ˜μ§€λ©΄?

Table of Contents

λ“€μ–΄κ°€λ©°

Kotlin을 톡해 κ°œλ°œμ„ ν•œ μ΄ν›„λ‘œ μ—λŸ¬κ°€ λ°œμƒν•  수 μžˆμŒμ„ μ•Œλ¦¬κ³  싢을 λ•Œ Resultλ₯Ό 적극적으둜 ν™œμš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

Java의 Checked Exceptionκ³Ό 같이 μ‚¬μš©ν•˜λŠ” μΈ‘μ—μ„œ μ˜ˆμ™Έκ°€ λ°œμƒν•  수 μžˆμŒμ„ 인지할 수 μžˆμœΌλ©΄μ„œλ„, κ°’μœΌλ‘œ 성곡/μ‹€νŒ¨λ₯Ό μ „λ‹¬ν•˜λŠ” νŠΉμ§• 덕뢄에 LSPλ₯Ό μœ„λ°˜ν•˜μ§€ μ•ŠλŠ” μž₯점이 μžˆλŠ”λ°μš”. 이 덕뢄에 νŠΉμ • λΉ„μ¦ˆλ‹ˆμŠ€ 둜직이 μ•„λ‹ˆλΌ κ³΅ν†΅μœΌλ‘œ μ‚¬μš©ν•˜λŠ” μ½”λ“œμ— κ°€κΉŒμšΈμˆ˜λ‘ 개인적으둜 Resultλ₯Ό 더 적극적으둜 ν™œμš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

μ΅œκ·Όμ— Resultμ—μ„œ μ œκ³΅ν•˜λŠ” κΈ°λ³Έ λ©”μ„œλ“œ 쀑 ν•˜λ‚˜μΈ onFailureμ—μ„œ μ˜ˆμ™Έλ₯Ό λ˜μ‘Œλ”λ‹ˆ λ°œμƒν–ˆλ˜ 일과, 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ νƒν•œ κ°„λ‹¨ν•œ 해결책을 닀뀄보렀 ν•©λ‹ˆλ‹€.

ν’λΆ€ν•˜κ³  λ‹€μ–‘ν•œ μ˜ˆμ™Έ

μ΅œκ·Όμ— λ“€μ–΄ μ˜ˆμ™Έλ₯Ό 잘 μƒμ†ν•˜μ—¬ λ‹€μ–‘ν•œ 상황에 맞좰 ν™œμš©ν•˜κ³ , κ·Έ λ‚΄μš©μ„ ν’λΆ€ν•˜κ²Œ μœ μ§€ν•˜κΈ° μœ„ν•΄ cause ν•„λ“œλ₯Ό ν™œμš©ν•˜λŠ” 것에 신경을 μ“°λ©° μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ €κ³  λ…Έλ ₯ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

특히 Sentry와 같은 도ꡬλ₯Ό μ‚¬μš©ν•  λ•Œ μ˜ˆμ™Έμ˜ Stack Traceλ₯Ό 잘 μͺΌκ°œμ„œ 보여주기 λ•Œλ¬Έμ— μ—λŸ¬ 좔적에 μžˆμ–΄ μœ μš©ν•˜κ³ , μ½”λ“œμ— μ€‘λ³΅μ μœΌλ‘œ λ‘œκΉ…μ„ ν•˜λŠ” 경우λ₯΄ 쀄일 수 μžˆμ–΄ λ§Žμ€ μž₯점을 느끼고 μžˆμŠ΅λ‹ˆλ‹€.

κ·ΈλŸ¬λ‹€ 보면 μ‚¬μš©ν•˜λŠ” λΌμ΄λΈŒλŸ¬λ¦¬μ— μ˜ν•œ μ˜ˆμ™Έλ‚˜ μš°λ¦¬κ°€ μ •μ˜ν•˜μ§€ μ•Šμ€ 상황에 λŒ€ν•œ μ˜ˆμ™Έκ°€ λ°œμƒν–ˆμ„ λ•Œ, cause에 잘 λ‹΄κ³ , μš°λ¦¬κ°€ μ •μ˜ν•œ μ˜ˆμ™Έλ₯Ό λ˜μ§€κ³  싢은 상황을 자주 λ§ˆμ£Όν•˜κ²Œ λ©λ‹ˆλ‹€.

try-catchλ₯Ό ν™œμš©ν•˜λ©΄ λ‹€μŒκ³Ό 같이 μ²˜λ¦¬ν•˜κ²Œ 될 것 κ°™μŠ΅λ‹ˆλ‹€.

fun retrieveFoo(): Foo {
    // Prepare values to retrieve foo
    // ...

    return try {
        fooClient.getFoo()
    } catch (e: Exception) {
        throw CustomOnlyForFooException(cause = e)
    }
}

fooClientκ°€ Spring WebClient, Ktor Client와 같이 μ™ΈλΆ€μ˜ μ˜μ‘΄μ„±μ΄μ–΄μ„œ μš°λ¦¬κ°€ μ•Œμ§€ λͺ»ν•˜λŠ” μ˜ˆμ™Έλ₯Ό 던질 수 μžˆκ² μ§€λ§Œ, CustomOnlyForFooExceptionμ΄λΌλŠ” Fooλ§Œμ„ μœ„ν•œ μ˜ˆμ™Έλ₯Ό μ •μ˜ν•΄μ„œ 쑰금 더 상황에 λΆ€ν•©ν•˜λŠ” ν’λΆ€ν•œ μ˜ˆμ™Έλ₯Ό λ˜μ§€λŠ” μƒν™©μž…λ‹ˆλ‹€. μ§€κΈˆμ€ μ˜ˆμ‹œμ—¬μ„œ cause만 λ‹΄μ•˜μ§€λ§Œ, Foo의 id, type 같은 정보λ₯Ό λ‹΄μ•„μ„œ 더 상황을 잘 ν‘œν˜„ν•˜λŠ” μ˜ˆμ™Έλ‘œ λ§Œλ“€ μˆ˜λ„ 있겠죠.

그리고 이 retrieveFoo()λ₯Ό μ‚¬μš©ν•˜λŠ” μ½”λ“œκ°€ μžˆλ‹€κ³  κ°€μ •ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. retrieveFoo()μ—μ„œ μ˜ˆμ™Έκ°€ λ°œμƒν•  수 μžˆλ‹€λŠ” 사싀을 μ•Œκ³  있으면 μ•„λ§ˆ try-catch둜 κ°μ‹Έμ„œ ν˜ΈμΆœμ„ ν•˜κ²Œ 될 κ²ƒμž…λ‹ˆλ‹€. 그리고 μ—¬κΈ°μ„œ λ°œμƒν•œ μ˜ˆμ™Έλ₯Ό λͺ…μ‹œμ μœΌλ‘œ μ²˜λ¦¬ν•΄μ£Όλ €λ©΄ λ‹€μ‹œκΈˆ CustomOnlyForFooException을 cause에 λ‹΄μ•„ μƒˆλ‘œμš΄ μ˜ˆμ™Έλ₯Ό λ˜μ Έμ€˜μ•Όκ² μ£ . μ—¬κΈ°μ„  CustomProcessFooException이라고 κ°€μ •ν•˜κ² μŠ΅λ‹ˆλ‹€.

fun processFoo(): ProcessFooResponse {
    return try {
        ProcessFooResponse.of(retrieveFoo())
    } catch (e: Exception) {
        throw CustomProcessFooException(cause = e)
    }
}

μ§€κΈˆμ€ μ˜ˆμ œκ°€ λ‹¨μˆœν•˜κ³  상황을 λ§Œλ“€κΈ° μœ„ν•œ 뢀뢄이 μžˆλ‹€λ³΄λ‹ˆ κ³Όν•œ 처리둜 보일 수 μžˆμ§€λ§Œ, λ ˆμ΄μ–΄κ°€ λ§Žμ•„μ§€κ³  κ³΅ν†΅μœΌλ‘œ μ‚¬μš©ν•˜λŠ”, ν˜Ήμ€ μ‚¬μš©λ˜λŠ” μ½”λ“œκ°€ λ§Žμ•„μ§€λ‹€ 보면 이런 접근이 μœ μš©ν•˜λ‹€κ³  λŠλΌλŠ” κ²½μš°κ°€ μ’…μ’… μžˆμŠ΅λ‹ˆλ‹€.

Result μ μš©ν•˜κΈ°

μœ„ μ½”λ“œλŠ” λ§Œμ•½ μ˜ˆμ™Έκ°€ λ°œμƒν•  경우 μ‚¬μš©ν•˜λŠ” μͺ½μ—μ„œλŠ” μ˜ˆμ™Έκ°€ λ°œμƒν•¨μ„ μ•Œ 수 μ—†λ‹€λŠ” λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€. Kotlinμ—λŠ” λͺ¨λ“  μ˜ˆμ™Έλ₯Ό Unchecked둜 닀루기 λ•Œλ¬Έμ—, μ‚¬μš©ν•˜λŠ” μΈ‘μ—μ„œ μ˜ˆμ™Έκ°€ λ°œμƒν•  수 μžˆμŒμ„ μ•Œλ €μ£ΌκΈ° μœ„ν•΄ Resultλ₯Ό μ‚¬μš©ν•˜κ²Œ λ©λ‹ˆλ‹€.

λ§Œμ•½ 직접 μ •μ˜ν•œ μ˜ˆμ™Έλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄ λ‹€μŒκ³Ό 같이 μ½”λ“œλ₯Ό μž‘μ„±ν•˜κ²Œ 될 κ²ƒμž…λ‹ˆλ‹€.

fun retrieveFoo(): Result<Foo> = runCatching {
    // Prepare values to retrieve foo
    // ...

    fooClient.getFoo()
}

fun processFoo(): Result<ProcessFooResponse> {
    return retrieveFoo().map { foo ->
        ProcessFooResponse.of(foo)
    }
}

μ§€κΈˆκΉŒμ§€λŠ” 큰 λ¬Έμ œκ°€ μ—†μ–΄ λ³΄μž…λ‹ˆλ‹€. Resultλ₯Ό ν™œμš©ν•˜μ—¬ 성곡할 경우 λ³€ν™˜μ„ μˆ˜ν–‰ν•˜κ³ , κ·ΈλŒ€λ‘œ Result의 ν˜•νƒœλ‘œ λ°˜ν™˜ν•˜λŠ” μ½”λ“œλ„€μš”.
그리고 결둠적으둜 processFoo()λ₯Ό μ‚¬μš©ν•˜λŠ” μͺ½μ—μ„œλ„ λ‹€μŒκ³Ό 같이 μ½”λ“œλ₯Ό μ‚¬μš©ν•˜κ²Œ 될 κ²ƒμž…λ‹ˆλ‹€. μš”λ²ˆμ—λŠ” κΈ°λ³Έκ°’ 처리λ₯Ό ν•˜λŠ” 경우둜 μƒκ°ν•΄λ³Όκ²Œμš”.

fun handleProcessFoo(): ProcessFooResponse {
    processFoo().getOrElse {
        logger.error(it) { "some error occurred" }
        ProcessFooResponse.DEFAULT
    }
}

λ³΄μ‹œλ‹€μ‹œν”Ό Result와 ν•¨κ»˜λΌλ©΄ ν•„μš”ν•˜λ‹€λ©΄ κΈ°λ³Έκ°’ 처리λ₯Ό ν•  μˆ˜λ„, ν•„μš”ν•˜μ§€ μ•Šλ‹€λ©΄ getOrThrow()둜 μ˜ˆμ™Έλ₯Ό λ˜μ Έλ²„λ¦΄ μˆ˜λ„ μžˆμ–΄ ν•œμΈ΅ μœ μ—°ν•œ μ²˜λ¦¬κ°€ κ°€λŠ₯ν•©λ‹ˆλ‹€.

직접 μ •μ˜ν•œ μ˜ˆμ™Έ 적용, 그리고 문제 λ°œμƒ

그러면 λ§Œμ•½ try-catchλ₯Ό μ“Έ λ•Œμ²˜λŸΌ μš°λ¦¬κ°€ μ •μ˜ν•œ μ˜ˆμ™Έλ₯Ό λ˜μ§€κ³  μ‹Άλ‹€λ©΄ μ–΄λ–»κ²Œ ν•  수 μžˆμ„κΉŒμš”?
λžŒλ‹€λ₯Ό 톡해 μ‹€νŒ¨ μ‹œ 처리λ₯Ό μ •μ˜ν•  수 μžˆλŠ” onFailureλ₯Ό ν™œμš©ν•˜λ©΄ μ–΄λ–¨κΉŒμš”?
ν•œλ²ˆ μ μš©ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

fun retrieveFoo(): Result<Foo> = runCatching {
    // Prepare values to retrieve foo
    // ...

    fooClient.getFoo()
}

fun processFoo(): Result<ProcessFooResponse> {
    return retrieveFoo().map { foo ->
        ProcessFooResponse.of(foo)
    }.onFailure { throw CustomProcessFooException(cause = it) }
}

fun handleProcessFoo(): ProcessFooResponse {
    processFoo().getOrElse {
        logger.error(it) { "some error occurred" }
        ProcessFooResponse.DEFAULT
    }
}

컴파일 μ—λŸ¬λ„ λ°œμƒν•˜μ§€ μ•Šκ³ , μ‹€μ œλ‘œ ꡬ동도 잘 λ©λ‹ˆλ‹€. ν•˜μ§€λ§Œ 이 μ½”λ“œμ—λŠ” λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€.
λ°”λ‘œ onFailureμ—μ„œ λ˜μ§„ CustomProcessFooException이 processFoo()의 결과값인 Result.failure<ProcessFooResponse>()κ°€ μ•„λ‹ˆλΌ μ•„μ˜ˆ processFoo()μ—μ„œ λ˜μ Έμ§„ μ˜ˆμ™Έκ°€ λœλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.
κ·Έλ ‡κΈ° λ•Œλ¬Έμ— handleProcessFoo()μ—μ„œ getOrElseλ₯Ό ν†΅ν•œ 핸듀링을 해도 μ „ν˜€ μ μš©λ˜μ§€ μ•Šκ²Œ λ©λ‹ˆλ‹€. μ• μ΄ˆμ— Resultκ°€ λ°˜ν™˜λ˜λŠ” 것이 μ•„λ‹Œ μ˜ˆμ™Έκ°€ 터져버리기 λ•Œλ¬Έμ΄μ£ .

κ·Έλ ‡λ‹€κ³  ν•΄μ„œ λ‹€μŒκ³Ό 같이 μ“Έ μˆ˜λ„ μ—†μŠ΅λ‹ˆλ‹€.

fun processFoo(): Result<ProcessFooResponse> {
    return retrieveFoo().map { foo ->
        ProcessFooResponse.of(foo)
    }.onFailure { Result.failure(CustomProcessFooException(cause = it)) }
}

onFailure의 μ‹œκ·Έλ‹ˆμ²˜λŠ” λ‹€μŒκ³Ό κ°™μ€λ°μš”.

inline fun <T> Result<T>.onFailure(action: (Throwable) -> Unit): Result<T>

action λžŒλ‹€μ˜ λ°˜ν™˜ν˜•μ΄ Unit이기 λ•Œλ¬Έμ— Result의 체이닝 결과에 영ν–₯을 쀄 수 μ—†κ²Œ λ˜μ–΄ 있기 λ•Œλ¬Έμž…λ‹ˆλ‹€. ꡳ이 해결해보렀면 inline funμ΄λΌλŠ” 점을 μ΄μš©ν•΄μ„œ λ‹€μŒκ³Ό 같이 직접 λ°˜ν™˜μ„ ν•΄μ£Όλ©΄ λ©λ‹ˆλ‹€.

fun processFoo(): Result<ProcessFooResponse> {
    return retrieveFoo().map { foo ->
        ProcessFooResponse.of(foo)
    }.onFailure { return Result.failure(CustomProcessFooException(cause = it)) }
}

ν•˜μ§€λ§Œ onFailure 뒀에 μ²΄μ΄λ‹μœΌλ‘œ 흐름이 더 μžˆλ‹€κ³  κ°€μ •ν•˜λ©΄ μ½”λ“œμ˜ 가독성이 λ–¨μ–΄μ§€λŠ” 뢀뢄이 되기 μ‰½μŠ΅λ‹ˆλ‹€.
μ–΄λ–»κ²Œ 보면 μ›λž˜ 체인의 흐름에 영ν–₯을 λ§Œλ“€μ§€ μ•ŠκΈ°λ‘œ κ·œμ •λ˜μ–΄ μžˆλŠ” κ³³μ—μ„œ 예기치 μ•Šμ€ 영ν–₯이 λ°œμƒν•˜λŠ” κ²ƒμ΄λ‹ˆκΉŒμš”.

해결책은 μ ‘λŠ” 것 (fold)

이런 λ¬Έμ œλŠ” foldλΌλŠ” λ©”μ„œλ“œλ₯Ό 톡해 ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€. (사싀 μ ‘λŠ”λ‹€κΈ° λ³΄λ‹€λŠ” μ˜ˆμ™Έκ°’κ³Ό 정상값을 ν•˜λ‚˜μ˜ νλ¦„μœΌλ‘œ μ€„μ—¬λ‚΄λŠ” 것에 κ°€κΉμŠ΅λ‹ˆλ‹€.)
fold의 μ‹œκ·Έλ‹ˆμ²˜λŠ” λ‹€μŒκ³Ό κ°™μ€λ°μš”.

inline fun <R, T> Result<T>.fold(onSuccess: (value: T) -> R, onFailure: (exception: Throwable) -> R): R

foldλŠ” 정상 μΌ€μ΄μŠ€μ™€ μ‹€νŒ¨ μΌ€μ΄μŠ€μ— λŒ€ν•΄ μ–΄λ–€ 값을 λ°˜ν™˜ν•  것인지λ₯Ό λžŒλ‹€λ₯Ό 톡해 μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
ꡳ이 Resultλ₯Ό λ°˜ν™˜ν•˜λ„λ‘ λͺ…μ‹œλœ 것은 μ•„λ‹ˆλ‹€ λ³΄λ‹ˆ, λ‹€μŒκ³Ό 같이 Result.success()와 Result.failure()λ₯Ό λͺ…μ‹œμ μœΌλ‘œ 써주긴 ν•΄μ•Όν•©λ‹ˆλ‹€.

fun retrieveFoo(): Result<Foo> = runCatching {
    // Prepare values to retrieve foo
    // ...

    fooClient.getFoo()
}

fun processFoo(): Result<ProcessFooResponse> {
    return retrieveFoo().fold(
        onSuccess = { foo -> Result.success(ProcessFooResponse.of(foo)) },
        onFailure = { Result.failure(CustomProcessFooException(cause = it)) }
    )
    
}

fun handleProcessFoo(): ProcessFooResponse {
    processFoo().getOrElse {
        logger.error(it) { "some error occurred" }
        ProcessFooResponse.DEFAULT
    }
}

ν•˜μ§€λ§Œ μ½”λ“œκ°€ 훨씬 λͺ…μ‹œμ μ΄κ²Œ 됐고, Result의 체이닝 흐름에도 영ν–₯을 μ£Όμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μžμ—°μŠ€λŸ½κ²Œ 계속 ν˜λŸ¬κ°€κ²Œ 되죠.

μ’€ 더 짧게!

foldλ₯Ό μ’€ 듀여닀보고 있으면 기본으둜 μ œκ³΅λ˜λŠ” getOrThrow μ—­μ‹œλ„ fold둜 ν‘œν˜„ν•  수 μžˆμŒμ„ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

inline fun <T> Result<T>.getOrThrowFold(): T =
    fold(
        onSuccess = { it },
        onFailure = { throw it }
    )

이와 λΉ„μŠ·ν•˜κ²Œ 쑰금만 더 μ‘μš©ν•΄λ³΄λ©΄ 성곡 값은 κ·ΈλŒ€λ‘œ 두고, μ‹€νŒ¨κ°’λ§Œ μš°λ¦¬κ°€ μ›ν•˜λŠ” μ˜ˆμ™Έλ‘œ λ³€ν™˜ν•΄μ„œ λ˜μ§€λŠ” ν™•μž₯ν•¨μˆ˜λ„ κ΅¬ν˜„ν•΄λ³Ό 수 있게 λ©λ‹ˆλ‹€.

inline fun <T, E : Throwable> Result<T>.mapFailure(
    transform: (Throwable) -> E
): Result<T> =
    fold(
        onSuccess = { Result.success(it) },
        onFailure = { Result.failure(transform(it)) }
    )

μ˜ˆμ™Έλ₯Ό λ³€ν™˜ν•˜λŠ” transform을 λžŒλ‹€λ‘œ λ°›λŠ” ν™•μž₯ν•¨μˆ˜λ₯Ό κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό μ½”λ“œμ— μ μš©ν•˜λ©΄ μ΅œμ’…μ μœΌλ‘œ λ‹€μŒκ³Ό 같이 μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

fun retrieveFoo(): Result<Foo> = runCatching {
    // Prepare values to retrieve foo
    // ...

    fooClient.getFoo()
}.mapFailure { CustomOnlyForFooException(cause = it) }

fun processFoo(): Result<ProcessFooResponse> {
    return retrieveFoo().map { foo ->
        ProcessFooResponse.of(foo)
    }.mapFailure { CustomProcessFooException(cause = it) }
    
}

fun handleProcessFoo(): ProcessFooResponse {
    processFoo().getOrElse {
        logger.error(it) { "some error occurred" }
        ProcessFooResponse.DEFAULT
    }
}

정상 μ½”λ“œμ˜ λ³€ν™˜κ³Ό μ˜ˆμ™Έ μ½”λ“œμ˜ λ³€ν™˜μ„ ν•œμΈ΅ 더 κΉ”λ”ν•˜κ²Œ ν‘œν˜„ν•  수 있게 λ˜μ—ˆλ„€μš”. 처음 try-catchλ₯Ό ν™œμš©ν•œ κ²ƒμ²˜λŸΌ retrieveFoo()에도 μ μš©ν•΄λ΄€μŠ΅λ‹ˆλ‹€.

마치며

μ–΄λ– μ‹ κ°€μš”?

크게 μ–΄λ ΅μ§€ μ•Šμ€ ν™•μž₯ ν•¨μˆ˜ κ΅¬ν˜„μ„ 톡해 Result의 체이닝을 μ§€μΌœλ‚΄λ©΄μ„œ μ½”λ“œλ₯Ό λͺ…μ‹œμ μœΌλ‘œ μž‘μ„±ν•  수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
onFailureμ—μ„œ μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚€κ²Œ 되면 ν•¨μˆ˜ μ „μ²΄μ˜ 호좜 μŠ€νƒμœΌλ‘œλΆ€ν„° μ˜ˆμ™Έκ°€ λ°œμƒν•˜κ²Œ λ˜λŠ” 뢀뢄은 예기치 λͺ»ν•œ 문제λ₯Ό 뢈러올 수 μžˆλŠ”λ°μš”. 특히 Coroutines ν™˜κ²½μ—μ„œ λ¬Έμ œκ°€ λ°œμƒν•˜λ©΄ Coroutines 특유의 μ—λŸ¬ μ „νŒŒλ‘œ 인해 μƒλ‹Ήνžˆ μΆ”μ ν•˜κΈ° κΉŒλ‹€λ‘œμš΄ 문제둜 λ²ˆμ§€κ²Œ λ©λ‹ˆλ‹€.

그런 상황이 λ°œμƒν•˜μ§€ μ•Šλ„λ‘ ν•˜λ €λ©΄ onFailure의 역할을 λͺ…ν™•ν•˜κ²Œ μ΄ν•΄ν•˜κ³ , ν•„μš”ν•œ κ²½μš°μ—” fold와 같은 λ‹€λ₯Έ λ©”μ„œλ“œλ₯Ό ν™œμš©ν•΄λ³΄λŠ”κ²Œ 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€.

이 글이 Resultλ₯Ό 슬기둭게 μ‚¬μš©ν•˜κ³ μž ν•˜λŠ” λΆ„λ“€κ»˜ 도움이 λ˜μ—ˆμœΌλ©΄ ν•©λ‹ˆλ‹€.

κ°μ‚¬ν•©λ‹ˆλ‹€.