본문 바로가기
Android/Kotlin

Kotlin Result 에러 핸들링

by 2Plus 2021. 11. 7.

Photo by Martin Shreder on Unsplash

 

반응형

 

ResultrunCatching은 오류를 처리할 수 있는 방법 중에 하나다. Result는 동작이 성공하든 실패하든 동작의 결과를 캡슐화해서 나중에 처리될 수 있도록 하는 것이 목적이다. 이 Result와 함께 사용할 수 있는 다양한 기능들이 표준 라이브러리에 추가되었다. Kotlin 1.3부터는 예외 처리를 위해 runCatching이라는 inline function도 제공된다. 함수형 프로그래밍 방식의 에러 처리도 가능하며 Coroutines에서 에러 처리를 할 때 구글이 권장하는 방식이기도 하다.

 

Result와 runCatching

 먼저 Result를 보면 이런 형태로 생겼다.

 

 runCatching은 이런 식으로 생겼다.

 

 ResultrunCatching을 보면 대충 어떤 느낌으로 동작할지는 감이 올 것이다. 이제 자세히 알아보자

 


try-catch, Rx와 비교

 먼저, 예제를 위해서 Exception을 던지는 함수를 하나 만들었다.

 

 try-catch를 사용하면 위와 같은 형태이다. Kotlin에서는 Java보다 조금 더 편해지기는 했지만, try-catch scope와 exception 처리, finally scope 구분 등이 여전히 번거롭다. 세부적인 exception마다 처리를 해주려면 catch block이 하나씩 계속 늘어난다.

 

 Rx의 경우는 이런 식으로 처리가 가능하다.

 

 runCatching을 사용하여 Result를 받아서 사용이 가능하다.

 

 Kotlin의 when 문법을 이용해서 에러 핸들링 가능하다. 근데 이렇게 사용하는 것보다 chaining을 사용한 다음 방법이 더 예쁘다.

 

 onFailure에서 모든 Throwable을 받아서 오류별로 케이스를 확인해서 처리가 가능하다. onFailure에서 오류를 던질 수도 있다.

 앞의 try-catch에서도 catch가 catch(exception: Exception)처럼 받고 내부에서 분기로 처리하면 비슷한 게 아니냐고 할 수 있다. 하지만 catch 구문 안에서 Throwable을 던지면 finally 구문이 있는 경우 여기서 Exception이 발생한다. runCatching의 onFailure는 Throwable를 던질 수 있다.

 

 Result에 chaining하여 이런 식으로 사용이 가능하다.

 

반응형

Datils about Result

Result Class는 4개의 함수와 property를 제공한다.

  • isSuccess : 성공 여부
  • isFailure : 실패 여부
  • getOrNull() : exception이 발생하지 않은 경우 해당 값, 발생한 경우는 null
  • exceptionOrNull() exception이 발생한 경우 해당 exception, 발생하지 않은 경우는 null

 이외에도 Result에 대한 다양한 extension이 존재하는데 이것들을 잘 사용하면 좋다. Result<T> 반환 타입의 경우 chaning하여 사용 가능하다. extension별 필요한 동작 내용들은 다음 문서에서 확인해서 활용하면 된다. (링크)

 


활용 예시

Android

 Android의 ViewModel에서 runCatching{}이 사용되는 예시이다. recover가 있어서 실패하더라도 에러 케이스 별로 원하는 값으로 변환도 가능하다.

 

How to handle Exceptions

 Exception을 복구하거나 기본값 또는 예비 옵션을 제공하는 등의 여러 방법이 제공된다.

 

 → statusResult.isSuccess는 true

 

 → fold는 다른 타입으로도 바꿀 수 있다.

 

반응형

주의 : Coroutines Breaker

 Corouintes와 runCatching {}을 함께 사용할 때 주의할 점이 있다.

 Coroutines에서는 suspend 지점에서 재개되기 전에 코루틴이 취소되었는지를 확인한다. 코루틴이 취소되었다면 CoroutineExceptionHandler가 CancellationException을 catch하며 suspend 지점 이후의 나머지 코루틴이 더 이상 실행되지 않는다.

 

 Android의 경우 데이터가 로드되는 동안 사용자가 back 버튼을 눌러서 현재 화면이 닫히면 suspend 부분에서 재개될 때 CancellationException을 확인하고 실행이 중단된다. 이런 식으로 중단되지 않는 경우에는 더 이상 없는 view에 대해 액세스하며 crash가 발생할 수 있다. runCatching 내에서 명시적으로 세부 예외 케이스들을 처리하지 않으면 CancellationException이 전파되지 않으며 코루틴이 중단되지 않는다. 우리가 알고 파악하고 있는 예외들에 대해서만 명시적으로 처리를 해주고 그렇지 않은 것은 다시 throw가 되도록 해야 한다.

 


Appendix : CoroutineExceptionHandler

 CoroutineContext를 선언할 때 CoroutineExceptionHandler를 추가할 수 있다. CoroutineExceptionHandler는 catch되지 않은 runtime exception들을 일관된 방식으로 한 곳에서 처리가 가능하도록 해주는 인터페이스이다. CoroutineExceptionHandler를 구현하여 val coroutineContext = Dispathcers.IO + coroutineExceptionHandler와 같이 추가해주면 된다.

 

 

References

https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html

https://kotlinlang.org/docs/exception-handling.html

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/

https://developer.android.com/kotlin/coroutines

https://stackoverflow.com/questions/143622/exception-thrown-inside-catch-block-will-it-be-caught-again

반응형

댓글