아래와 같이 함수가 원하는 결과를 만들어 낼 수 없을 때가 존재한다.
- 서버로부터 데이터를 읽어 들이려고 했는데, 인터넷 연결 문제로 읽어들이지 못한 경우
- 조건에 맞는 첫 번째 요소를 찾으려 했는데, 조건에 맞는 요소가 없는 경우
- 텍스트를 파싱해서 객체를 만들려고 했는데, 텍스트의 형식이 맞지 않는 경우
위와 같은 상황을 처리하는 메커니즘은 크게 두가지가 있다.
- null 또는 실패를 나타내는 sealed 클래스(일반적으로 Failure라는 이름 사용)
- 예외 throw
예외를 throw 하는 경우 예외를 정보를 전달하는 방법으로 사용하면 안된다. 예외는 예외적인 상황이 발생했을 때 사용하는 것이 좋다. 이유는 아래와 같다.
- 많은 개발자가 예외가 전파되는 과정을 제대로 추적하지 못한다.
- 코틀린의 모든 예외는 unchecked 예외이며 따라서 사용자가 예외를 처리하지 않을수도 있다. 이와 관련된 내용은 문서에도 제대로 제대로 드러나지 않는다.
- 예외는 예외적인 상황을 처리하기 위해 만들어졌으므로 명시적인 테스트 만큼 빠르게 동작하지 않는다.
- try-catch 블록 내부에 코드를 배치하면, 컴파일러가 할 수 있는 최적화가 제한된다.
null 또는 sealed 클래스를 활용한 오류 처리
위에서 다룬 첫번째 방법인 null 또는 실패를 나타내는 sealed 클래스를 사용하는 방법은 오류를 표현할 때 굉장히 좋다.
예측할 수 있는 범위의 오류는 null과 Failure를 사용하고 예측하기 어려운 범위의 오류는 예외를 throw 해서 처리하자.
AS-IS
inline fun <reified T>.readObjectOrNull(): T? {
// ...
if (incorrectSign) {
return null
}
// ..
return result
}
TO-BE
inline fun <reified T>.readObjectOrNull(): T? {
// ...
if (incorrectSign) {
return Failure(JsonParsingException()
}
// ..
return Success(result)
}
sealed class Result<out T>
class Success<out T>(val result: T): Result<T>()
class Failure(val throwable: Throwable): Result<Nothing>()
class JsonParsingException: Exception
위와 같이 표시되는 오류는 다루기 쉬우며 놓치기 어렵다.
val age = userText.readObjectOrNull<Person>()?.age ?: -1
null을 처리해야 하면 위의 safe call(?) 또는 elvis operator(?:)와 같은 다양한 null-safety 기능을 활용하자.
val person = userText.readObjectOrNull<Person>()
val age = when(person) {
is Success -> person.age
is Failure -> -1
}
Result와 같은 Union type을 리턴하기로 했다면 위의 when 표현식을 사용해 이를 처리할 수 있다.
위에서 설명한 오류 처리 방식은 try-catch 블록보다 효율적이며, 사용하기 쉽고 명확하다. 예외는 놓칠 수 있고, 전체 애플리케이션을 중지시킬 수 있지만 null 값과 seald result 클래스는 명시적으로 처리해야 하며 애플리케이션을 중지시키지도 않는다.
null과 sealed class 차이점 추가적인 정보 전달이 필요하면 sealed result를 사용하고, 그렇지 않으면 null을 사용하는게 일반적이다. Failure는 처리할 때 필요한 정보를 가질 수 있다.
List에서는 아래와 같은 형태의 함수를 사용한다.
-
get
해당 요소가 없다면 IndexOutOfBoundsException(List 기준)
-
getOrNull
해당 요소가 없다면 null
위 두가지 함수 이외에 일부 유용한 getOrDefault도 존재.
일반적으로 getOrNull 또는 Elvis operator(?:)를 사용하는 것이 처리하기 쉽다.
정리
- 예측하기 어려운 범위가 아닌 경우라면 null 또는 sealed 클래스를 활용하자
- 개발자에게 null이 발생할 수 있다고 경고를 주려면 getOrNull 등을 사용해 무엇이 리턴되는지 예측할 수 있게 하는 것이 좋다.
Reference
- 이펙티브 코틀린 - 프로그래밍 인사이트, 마르친 모스칼라 지음, 윤인성 옮김
개인적인 기록을 위해 작성된 글이라 잘못된 내용이 있을 수 있습니다.
오류가 있다면 댓글을 남겨주세요.