Kotlin 표준 라이브러리에서는 Result 클래스를 제공한다. Result는 실행한 동작의 결과를 Result로 감싸서 처리를 다른 클래스에 위임하는 것이 목적인 클래스이다. Result와 Result의 확장함수를 통해 유연하게 성공 및 실패를 처리할 수 있다. 작업을 수행하기 위한 방법으로는 대표적인 try-catch도 있고, Kotlin 1.3v에서 추가된 Result를 반환하는 inline 함수, runCatching도 있다. 이번 포스팅에서는 Result, 여러가지 Result 함수를 소개한다.
사전 지식 : runCatching
runCatching은 코틀린 1.3v에서 도입된 캡슐화 블록이다. runCatching 블록 안에서 성공/실패 여부가 캡슐화된 Result<T> 형태로 반환한다. try-catch로직과 유사하며, Result로 감싸서 반환하는 것이 특징이다.
Result 소개 : "Hello, Result!"
Result는 실행의 결과를 캡슐화한 클래스로, 성공시에는 성공한 결과로 캡슐화를, 실패시에는 Throwable한 예외와 함께 실패를 캡슐화하는 discriminated union(구별되는 조합)이다.
Result와 runCatching을 활용하면, 실행한 결과를 캡슐화 하여 다른 클래스에 결과 처리를 위임할 수 있다.
Result 프로퍼티
Result에는 두 가지 Properties, isSuccess 와 isFailure 가 있다.
public val isSuccess: Boolean get() = value !is Failure
public val isFailure: Boolean get() = value is Failure
만약 Result가 failure로 캡슐화 되어 있다면, isFailure = true / isSuccess = false가 된다.
반대로 suceess로 캡슐화 되어 있다면, isSuccess = true / isFailure = false가 된다.
Result 멤버 함수
Result는 총 세 가지 멤버 함수를 가지고 있다.
public inline fun getOrNull(): T? =
when {
isFailure -> null
else -> value as T
}
public fun exceptionOrNull(): Throwable? =
when (value) {
is Failure -> value.exception
else -> null
}
public override fun toString(): String =
when (value) {
is Failure -> value.toString() // "Failure($exception)"
else -> "Success($value)"
}
해당 코드를 확인하면,
- getOrNull은 성공은 일반적인 경우와 동일하고, 실패 시 null을 반환한다.
- exceptionOrNull은 실패는 일반적인 경우와 동일하고, 성공시 null을 반환한다
- toString은 성공 시 "Success(value.toString())"을 반환하고, 실패 시 "Failure(exception.toString())"을 반환한다.예제를 통해 동작을 확인할 수 있다.
아래의 예시를 통해 동작을 확인할 수 있다.
fun main() {
runCatching{
"jainoException".toInt()
}.getOrNull() ?: println("get null if it is failure")
val resultExample = runCatching{
"Hello Result"
} // > get null if it is failure
resultExample.exceptionOrNull() ?: println("get null if it is success") // > get null if it is success
println(resultExample.toString()) // > Success(Hello Result)
}
Result 확장 함수
Result의 확장함수는 다양한 기능을 제공하는데, 필자가 임의로 나눈 타입에 따라서 소개하겠다.
1. Result 실패시 사용할 수 있는 확장함수.
fun <T> Result<T>.getOrThrow(): T
fun <R, T : R> Result<T>.getOrDefault(defaultValue: R): R
inline fun <R, T : R> Result<T>.getOrElse(onFailure: (exception: Throwable) -> R): R
inline fun <R, T: R> Result<T>.recover(transform: (exception: Throwable) -> R): Result<R>
inline fun <R, T: R> Result<T>.recoverCatching(transform: (exception: Throwable) -> R): Result<R>
inline fun <T> Result<T>.onFailure(action: (exception: Throwable) -> Unit): Result<T>
- getOrThrow : 실패 시, 발생한 예외를 밖으로 버린다.
- getOrDefault : 실패 시, 지정한 Default 파라미터를 반환한다.
- getOrElse : 실패 시, 지정한 Else를 반환한다. Exception을 반환받고, 예외 처리를 할 수 있다.
- getOrDefault와 같은 작업을 수행하며, getOrElse는 Exception에 따라 유연하게 처리할 수 있다는 장점이 있다.
- recover : 실패 시, 예외를 반환 받고, 예외와 관련된 처리를 할 수 있다. 반환 시에 캡슐화 된 타입과 같아야 하며, 반환된 값은 성공으로 간주된다.
- recoverCatching : recover과 동작은 차이가 없다. 그러나 변환 시에 에러가 발생한 경우, recover는 실패를 외부로 throw하고, recoverCatching은 실패로 처리한다.
- onFailure : 실패 시에만, 호출되는 함수로 예외를 받아, 예외 처리를 진행할 수 있다.
예제를 통해 동작을 확인할 수 있다.
fun main() {
val resultExample = runCatching{
"Hello Result".toInt()
}
// getThrow() 실패 시 throw
try {
resultExample.getOrThrow()
} catch (e: Exception) {
println("0") // > 0
}
// getOrDefault() 실패 시 지정한 default값 반환
resultExample.getOrDefault(1).let { println(it) } // > 1
// getOrElse() 실패 시 지정한 else 반환
resultExample.getOrElse { println(2) } // > 2
}
class JainoException : Exception()
fun throwJainoException(): String = throw JainoException()
fun main() {
// 일반적인 recover, recoverCatching
runCatching { throwJainoException() }
.recoverCatching { "Success Jaino" }
.onSuccess { println(it) } // > "Success Jaino"
// map : 변환과정 중 실패 시 외부로 throw
try {
runCatching { throwJainoException() }
.recover { throwJainoException() } // 변환시 에러 발생
.onSuccess { println(it) } // 호출되지 않음.
.onFailure { println(it) } // 호출되지 않음.
} catch (e: JainoException) {
println(e) // > JainoException
}
// mapCatching : 변환과정 중 실패 시 내부의 onFailure로 처리
runCatching { throwJainoException() }
.recoverCatching { throwJainoException() } // 변환시 에러 발생
.onFailure { println(it) } // > JainoException
}
2. Result 성공시 사용할 수 있는 확장 함수
inline fun <R, T> Result<T>.map(transform: (value: T) -> R): Result<R>
inline fun <R, T> Result<T>.mapCatching(transform: (value: T) -> R): Result<R>
inline fun <T> Result<T>.onSuccess(action: (value: T) -> Unit): Result<T>
- map : 성공 시 성공한 값을 반환하며, 해당 값에 대해서 추가적인 변환을 수행하는 함수이다.
- mapCatching : map과 동일한 작업을 수행하며, 만약 변환 시에 에러가 발생한 경우, map은 외부로 throw하고, recoverCaching은 실패로 처리하고, onFailure로 받을 수 있다.
- onSuccess : 성공시에만 호출되는 함수이며, 성공한 값을 반환 받는다.
예제를 통해 확인할 수 있다.
class JainoException : Exception()
fun throwJainoException(): String = throw JainoException()
fun main() {
// 일반적인 mapCatching 성공 시 변환
runCatching{ "Success Jaino" }
.mapCatching{ "Hello Jaino" }
.onSuccess{ println(it) } // > Hello Jaino
// map : 변환과정 중 실패 시 외부로 throw
try {
runCatching { "Success Jaino" }
.map { throwJainoException() }
.onSuccess { println(it) } // 호출되지 않음.
.onFailure { println(it) } // 호출되지 않음.
} catch (e: JainoException) {
println(e) // > JainoException
}
// mapCatching : 변환과정 중 실패 시 onFailure로 처리
runCatching { "Success Jaino" }
.mapCatching { throwJainoException() }
.onFailure { println(it) } // > JainoException
}
3. 성공과 실패시 사용할 수 있는 확장 함수
inline fun <R, T> Result<T>.fold(onSuccess: (value: T) -> R, onFailure: (exception: Throwable) -> R): R
- fold : 작업이 성공했을 경우에는 인수 onSuccess로 정의한 동작을 수행하고, 실패했을 경우에는 인수 onFailure에 정의한 동작을 실행한다.
예제를 통해 동작을 확인할 수 있다.
class JainoException : Exception()
fun throwJainoException(): String = throw JainoException()
fun main() {
// fold 성공과 실패 모두 정의
runCatching { throwJainoException() }.fold(
onSuccess = { println("Success Jaino") },
onFailure = { println("Failure Jaino") } // > Failure Jaino
)
runCatching { "Hello Jaino" }.fold(
onSuccess = { println("Success Jaino") },
onFailure = { println("Failure Jaino") } // > Success Jaino
)
}
마지막으로
Result와 같은 결과 및 예외를 처리하는 클래스는 실제로 사용자가 직접 구성하고 구현할 수 있다. 하지만 추가적인 작업에 대한 함수를 구현하는 것은 큰 리소스를 필요로 한다. Result의 경우 다양한 상황에서 필요로하는 작업(에러 핸들링 위임, 이벤트 흐름 처리)을 미리 정의된 함수로 간편하게 처리할 수 있다. 구글에서도 코루틴을 처리위한 방법으로 Result를 추천하고 있고 기회가 된다면 실제 프로젝트에 도입하는 것을 추천한다.
참고 문헌
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/
Result - Kotlin Programming Language
kotlinlang.org
https://dev-repository.tistory.com/105
Kotlin Result 에러 핸들링
Result와 runCatching은 오류를 처리할 수 있는 방법 중에 하나다. Result는 동작이 성공하든 실패하든 동작의 결과를 캡슐화해서 나중에 처리될 수 있도록 하는 것이 목적이다. 이 Result와 함께 사용할
dev-repository.tistory.com
https://medium.com/harrythegreat/kotlin-runcatching%EA%B3%BC-result-%ED%83%80%EC%9E%85-ab261f47efa8
[Kotlin] runCatching과 Result 타입
runCatching은 코틀린 1.3버전부터 도입된 Result 캡슐화 함수입니다. runCatching 블록 안에서 성공/실패 여부 캡슐화된 Result 형태로 리턴합니다. 스위프트의 Result, 자바스크립트의 Promise와 유사하며…
medium.com
https://rannte.tistory.com/entry/kotlinruncatching
[번역][Kotlin] Try-catch를 쓰기 힘들다면 runCatching을 써보자! - Qiita
ㅅException이 Throw여부에 따라 각각 어떤 코드를 실행하고 싶을때가 있죠. try-catch(-finally)로 구현하기 힘들때가 있습니다. try-catch(-finally)는 Exception이 Throw되었을때의 처리(또는 throw여부에 관
rannte.tistory.com