아이템 08. 적절하게 Null을 처리하라

아이템 08. 적절하게 Null을 처리하라

적절하게 null을 처리하라 #

null은 ‘값이 부족하다(lack of value)‘를 의미한다. 프로퍼티가 null이 라는 것은 값이 제대로 설정되지 않았다거나, 제거됐다는 것을 의미한다.

nullable 은 최대한 명확하게 처리해야한다. (String.toIntOrNull(), Iterable<T>.firstOrNull(() -> Boolean))

기본적으로 nullable 타입은 3 가지 방법으로 처리한다.

  1. ?., ?:, 스마트 캐스팅 등을 활용해서 처리한다.
  2. 오류를 throw 한다.
  3. (함수, 프로퍼티 리팩터링하여) non-nullable로 변경한다.

null 안전하게 처리 #

safe call (?., ?:), 스마트 캐스팅을 활용해서 null 을 안전하게 처리할 수 있다.


오류 throw #

throw, !!, requireNotNull, checkNotNull 등을 활용해서 명시적으로 non-null 을 표기하고, 예외를 발생시킬 수 있다.

!! 은 사용하기 쉽지만, 좋은 해결 방법은 아니다. !!은 예외가 발생할 때 어떤 설명도 없는 제네릭 예외(generic exception)이 발생한다. (명시적 오류가 generic exception보다 더 많은 정보를 제공한다.)

!!은 null이 나오지 않는다는 것이 거의 확실한 상황에서 많이 사용된다. (!!을 남용하지 말자.)

" 코틀린을 대상으로 설계된 API를 활용한다면, !!연산자를 사용하는 것을 이상하게 생각해야 합니다. 일반적으로 !! 연산자 사용을 피해야 합니다. 이러한 제안은 코틀린 커뮤니티 전체에서 널리 승인되고 있는 제안입니다. “


non-nullable #

nullability는 어떻게든 처리해야 하므로 비용이 발생한다. 필요한 경우가 아니라면 nullability 자체를 피하는 것이 좋다.

  • nullability 에 따라 (구분되는)함수를 만들어 사용/제공할 수 있다.
  • 어떤 값이 클래스 생성 이후에 확실하게 설정된다는 보장이 있다면, lateinit, Delegates.notNull 를 사용한다.
  • 빈 컬렉션 대신 null 을 리턴하지 않는다.
  • nullable enum 과 None enum 은 완전히 다르다. (= nullable enum 보다 None enum 사용을 권장)

lateinit 프로퍼티 #

lateinit 한정자는 프로퍼티가 이후에 설정될 것임을 명시하는 한정자다. lateinit은 프로퍼티를 처음 사용하기 전에 반드시 초기화될 것이라고 예상되는 상황에서 활용한다.

class UserControllerTest {
    private lateinit var dao: UserDao

    @BeforeEach
    fun init() {
        dao = mockk()
        ...
    }
}

물론 lateinit 사용도 비용이 발생한다. 초기화 전에 해당 변수를 사용하면 예외가 발생한다. 무서워할 수 있지만, 어떻게 보면 이것은 ‘빠른 실패’로 오히려 좋은 것이다.


Delegates.notNull #

lateinit을 사용할 수 없는 경우도 있다. JVM에서 Int, Long, Double, Boolean과 같은 기본 타입과 연결된 타입은 사용할 수 없다. 이런 경우 lateinit 보다 약간 느리지만, Delegates.notNull 을 사용한다.

:star: 새롭게 알게 된 내용 : JVM에서 Int, Long, Double, Boolean과 같은 기본 타입과 연결된 타입은 사용할 수 없다.

class DoctorActivity: Activity() {
    private var doctorId: Int by Delegates.notNull()
    private var fromNotification: Boolean by Delegates.notNull()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        doctorId = ...
        fromNotification = ...
    }
}

다음과 같이 프로퍼티 위임(property delegation)을 사용할 수도 있다.

class DoctorActivity: Activity() {
    private var doctorId: Int by arg(DOCTOR_ID_ARG)
    private var fromNotification: Boolean by arg(FROM_NOTIFICATION_ARG)
}

” 프로퍼티 위임을 사용하면, nullability로 발생하는 여러 가지 문제를 안전하게 처리할 수 있다. (아이템 21에서 자세하게 다룰 예정) “