본문 바로가기

안드로이드/Kotlin

3. Effective Kotlin - 결과 부족 시 null & Failure를 사용하라

결과 부족 시 null & Failure를 사용하라

함수가 원하는 결과를 만들지 못하는 경우가 있다. 예를 들면 다음과 같다.

  • 서버로부터 데이터를 읽어들이는 과정에서 네트워크 오류로 읽어들이지 못한 경우
  • 조건에 맞는 첫 번째 요소를 찾으려 했으나 없는 경우

위와 같은 상황을 핸들링하는 방법은 크게 두 가지이다.

  • null 혹은 _Failure_를 나타내는 _sealed class_를 사용하라.
  • 예외를 throw한다.

위는 두 가지 차이점이 있다. 먼저, 예외는 정보를 전달하는 목적이 아니다. 반면에, _null_과 _Failure_는 정보 전달의 역할을 충분히 수행한다. 가능하면 nullable을 리턴하지 않는 것이 좋다. null을 리턴해야되는 경우 개발자에게 getOrNull 등을 사용해서 무엇이 리턴되는지 명확히 해야된다.

sealed class Result<out T>
class Success<out T>(val result: T): Result<T>()
class Failure<out T>(val throwable: Throwable): Result<T>()

inline fun <reified T> String.readObject(): Result<T> {
    if(incorrectSign) {
        return Failure(JsonParseException())
    }
    ...
    return Success(result)
}
val person = userText.readObject<Person>()
val age = when(person) {
    is Success -> person.age
    is Failure -> -1
}

적절하게 null을 처리하라

null은 '값의 부족'을 나타낸다. 프로퍼티가 null이라는 것은 값이 설정되지 않았거나, 제거되었다는 것을 의미한다.

null은 최대한 명확한 의미를 갖는 것이 좋다. 이 nullable을 처리하는 것은 결국 개발자이기 때문이다.

nullable 타입을 처리하는 방법은 크게 세 가지가 있다.

  • ?., 스마트 캐스팅, 앨비스 연산자를 활용한다.
  • 오류를 throw 한다.
  • 함수, 프로퍼티를 리팩토링하여 nullabllity를 제거한다.

not-null assertion(!!)과 관련된 문제

nullable을 처리하는 가장 쉬운 방법이다. 하지만, 이 방법만 활용하면 기존 Java의 NPE 문제를 전혀 해결하지 못한다. 이를 사용하면 언젠가 해당 코드가 에러를 발생시킬 수 있음에 유의해야 한다. 따라서, 이런 사용을 피하는 것을 가장 권장한다.

lateinit과 notNull delegate

클래스가 생성 중에 초기화할 수 없는 프로퍼티를 가지는 것은 드물지만 분명히 존재한다. 하지만, 이를 위해서 nullable하게 만드는 것은 좋지 않다. 이 문제의 대안은 lateinit을 활용하는 것이다. 하지만, 초기화 전에 사용하면 안된다는 점은 유의해야 한다.

Primitive 타입은 lateinit을 지원하지 않는다. 이 경우에는 Delegates.notNull을 사용할 수 있다.

```kotlin
class MainActivity: Activity() {
private var doctorId: Int by Delegates.notNull()

override fun onCreate(savedInstanceState: Bundle?) {
...
doctorId = intent.extras.getInt(DOCTOR_ID_ARG)
}
}