λ€μ΄κ°λ©°
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): Rfoldλ μ μ μΌμ΄μ€μ μ€ν¨ μΌμ΄μ€μ λν΄ μ΄λ€ κ°μ λ°νν κ²μΈμ§λ₯Ό λλ€λ₯Ό ν΅ν΄ μ μν μ μμ΅λλ€.
κ΅³μ΄ 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λ₯Ό μ¬κΈ°λ‘κ² μ¬μ©νκ³ μ νλ λΆλ€κ» λμμ΄ λμμΌλ©΄ ν©λλ€.
κ°μ¬ν©λλ€.