HTTP 200 OK

Memento mori & Carpe diem

Kotlin

[Kotlin] Result란?

sjoongh 2023. 9. 12. 15:20

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