kyucumber
전체 글 보기

이펙티브 코틀린 아이템 07. 결과 부족이 발생할 경우 null과 Failure를 사용하라

아래와 같이 함수가 원하는 결과를 만들어 낼 수 없을 때가 존재한다.

  • 서버로부터 데이터를 읽어 들이려고 했는데, 인터넷 연결 문제로 읽어들이지 못한 경우
  • 조건에 맞는 첫 번째 요소를 찾으려 했는데, 조건에 맞는 요소가 없는 경우
  • 텍스트를 파싱해서 객체를 만들려고 했는데, 텍스트의 형식이 맞지 않는 경우

위와 같은 상황을 처리하는 메커니즘은 크게 두가지가 있다.

  • 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

  • 이펙티브 코틀린 - 프로그래밍 인사이트, 마르친 모스칼라 지음, 윤인성 옮김

개인적인 기록을 위해 작성된 글이라 잘못된 내용이 있을 수 있습니다.

오류가 있다면 댓글을 남겨주세요.