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๋ฅผ ์Šฌ๊ธฐ๋กญ๊ฒŒ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ๋ถ„๋“ค๊ป˜ ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉด ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.