Result란?
- Result는 동작이 성공하든 실패하든 동작의 결과를 캡슐화하여 나중에 처리할 수 있도록 하는 것이 목적입니다.
- Result 클래스는 runCatching 함수의 반환형이며 코루틴에서 에러 처리를 할 때 권장하는 방식이기도 합니다.
- runCatching 블록 안에서 성공/실패 여부가 캡슐화된 Result<T> 형태로 리턴합니다.(Js의 Promise와 유사)
- Result 클래스 멤버
isSuccess & isFailure
블록의 실행이 성공하면 (블록내에서 예외처리가 발생하지 않았을때) isSuccess 변수를 확인하여 알 수 있습니다. 역으로 블록의 실행이 실패했다면 isFailure 변수를 통해 확인 할 수 있습니다.
if (colorName.isSuccess) { //성공시 호출 }
if (colorName.isFailure) { //성공시 호출 }
getOrNull & exceptionOrNull
블록의 반환값을 얻기 위해선 getOrNull 함수를 사용합니다.블록내에서 예외가 발생한 경우에는 null이 반환됩니다.
블록내에서 throw된 exception을 얻기 위해선 exceptionOrNull 함수를 사용합니다.블록내에서 예외가 발생하지 않은 경우에는 null이 반환됩니다.
- Result 클래스의 확장함수
get 계열 함수
runCatching의 return 값이 필요할때 사용
- getOrNull : 반횐된 Result 객체가 성공 일때 값 반환, 실패면 null
- getOrThrow : 반횐된 Result 객체가 성공 일때 값 반환, 예외가 발생하면 그대로 throw
- getOrDefault : 반횐된 Result 객체가 성공 일때 값 반환, 실패하면 디폴트 값을 반환
- getOrElse : 반횐된 Result 객체가 성공 일때 값 반환, 실패하면 지정된 코드 실행
on 계열 함수
이 함수들은 리시버의 Result 오브젝트를 그대로 반환하기 때문에 체인 호출이 가능
- onSuccess(try) : runCatching 함수 블록의 실행이 성공했을때 실행
- onFailure(catch) : runCatching 함수 블록의 실행이 실패했을때 실행
- also를 함께 사용하면 finally와 같은 동작을 한다.
map
성공한 값에 대해 한번 더 가공이 가능
- map에서 에러가 발생할 경우 runCatching 구문 밖으로 예외처리. → try, catch로 다시 잡아줘야함
mapCatching
성공한 값에 대해 한번 더 가공이 가능
- 여기서 예외가 발생하면 runCatching 구문 밖으로 예외를 던지지 않고, 그 예외를 담은 Result 객체 반환, 이후 onFailure 등으로 다시 처리 가능
recover
실패한 값에 대해 한번 더 핸들링이 가능
- recover 에서 에러가 발생할 경우 runCatching 구문 밖으로 예외처리
recoverCatching
실패한 값에 대해 한번 더 핸들링이 가능
- 예외가 발생하면 runcatching 구문밖으로 예외를 던지지 않고 그 예외를 담은 Result 객체 반환, 이후 onFailure 등으로 다시 처리 가능
fold
- runCatching 함수가 성공했을 경우에는 인수 onSuccess로 주어진 동작을 수행하고, 실패했을 경우에는 인수 onFailure에 주어진 동작을 실행합니다.
- 위에서 소개한 함수 onSuccess 와 onFailure 를 양쪽 다 호출하는 것은 비슷하지만, 한가지 큰 차이점은 반환값입니다.
- fold 함수는 성공했을 경우와 실패했을 경우의 동작이 함께 같은 형태의 반환값을 갖도록 되어 있고 그것이 fold 함수의 반환값이 됩니다.
try-catch를 runCatching으로 변환
val fruitName =try {
getRandomFruit()
}catch (throwable:Throwable) {
""
}
- 이런 방식으로 Result을 함수의 return type으로 줄 수 있다.
- 다른 method에서 해당 함수를 호출하여 onSuccess, getOrThrow와 같은 다양한 Result확장함수를 추가적으로 선언해주는 방식 또한 존재한다.
val fruitResult =runCatching {
getRandomString()
}
val fruitName = fruitResult.getOrNull()
// if (fruitResult.isSuccess) { }
// if (fruitResult.isFailure) { }
// val fruitName = fruitResult.getOrNull()
// val throwable = fruitResult.exceptionOrNull()
- getOrNull으로 exception이 발생하지 않는 경우 value를, 그 외는 null을 받을 수 있고,
- exceptionOrNull은 그 반대입니다.
runCatching사용 시 여러 변수에 대한 throw
fun finishShipping(claimOrderLineIds: List<String>) =runCatching{
if (this.type == ClaimType.CANCEL) this.invalidType()
if (this.status != ClaimStatus.IN_PROGRESS) this.invalidStatus()
this.claimOrderLines.filter{col->claimOrderLineIds.contains(col.id.toString())}.forEach{col->col.finishShippingClaimOrderLine()}
}
private fun invalidType(): Nothing {
throw IllegalArgumentException("Invalid type - current: ${this.type.name}.")
}
private fun invalidStatus(): Nothing {
throw IllegalArgumentException("Invalid status - current: ${this.status.name}.")
}
- 단순한 비즈니스일 경우 onSuccess와 onFailure을 사용하면 되지만 변수를 검증하고 create하는 비즈니스의 경우에는 onFailure로는 한계가 존재하기에 throw 문을 블록 내부에 만드는 방법을 사용했다.
Nothing 란?
runcatching { doSomethingSync() }
.onFailure {
when(it) {
is IllegalArgumentException ->
showProperFeedback()
is IllegalStateException ->
handleIllegalStateException()
else ->
throw it
}
}
.onSuccess { processData(it) }
- Result에 chaining하여 사용이 가능하다.
Result를 사용하는 경우
- 외부 서비스에 의존하는 로직이라 예외 발생 가능성이 빈번한 컴포넌트
- 해당 컴포넌트에서 에러가 발생할 수 있다는 것을 클라이언트에게 알려주고 싶을 때, 에러 핸들링을 다른 컴포넌트에 강제하고 위임하고 싶을 때
- try … catch를 쓰고 싶지 않을 때
- 예외상황 발생시 try~catch(예외를 잡아내는 역할)는 사용자가 흐름을 능동적으로 제어할 수 있고 throws(예외를 강제로 발생)는 강제종료 되기 때문에 수동적이다.
throw 사용
- 올바른 변수에 대한 검증을 할 때, null or 원하지 않는 값이 들어왔을 경우
- CRUD 수행 시 단일 값에 대한 검증을 수행할 때
- try …catch에서 error를 던지고 싶을 때
try …catch와 global Exception의 차이
- try …catch와 Result는 개발자가 발생 가능성이 있는 예외상황을 예측하여 예외에 대한 처리를 미리 정의하는 것이다. → 즉 개발자가 파악하고 있는 예외상황들이다.
전역 에러 핸들러(Global Exception Handler) 란?
- @ControllerAdvice / @RestControllerAdvice과 @ExceptionHandler 어노테이션을 기반으로 Controller 내에서 발생하는 에러에 대해서 해당 핸들러에서 캐치하여 오류를 발생시키지 않고 응답 메시지로 클라이언트에게 전달해주는 기능을 의미한다.
- Global Exception은 주로 API 통신시 간단한 오류 메시지를 표시해야 하는 경우에 사용하며
- 동일한 예외처리를 하는 경우에도 자주 사용된다.
출처
https://toss.tech/article/kotlin-result
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/
'Kotlin' 카테고리의 다른 글
JSON 직렬화가 왜 안돼?.. (1) | 2024.11.24 |
---|---|
[Kotlin] Error Handling (0) | 2023.08.26 |
Run Catching (0) | 2023.03.01 |
[Kotlin] 기초 (0) | 2022.08.01 |