kyucumber
전체 글 보기

이펙티브 코틀린 아이템 16. 프로퍼티는 동작이 아니라 상태를 나타내야 한다.

코틀린의 프로퍼티와 자바의 필드는 비슷해 보이지만 완전히 다른 개념이다. 코틀린의 프로퍼티는 아래처럼 사용자 정의 setter, getter를 가질 수 있다.

var name: String? = null get() = field?.toUpperCase() set(value) { if (!value.isNullOrBlank() }

위 코드의 field라는 식별자는 프로퍼티의 데이터를 저장해두는 backing field에 대한 레퍼런스이다.

val fullName: String get() = "$name $username"

val을 사용해 읽기 전용 프로퍼티를 만들때는 field가 만들어지지 않는다.

var을 사용해 만든 읽고 쓸수 있는 프로퍼티는 getter, setter를 정의할 수 있다. 이러한 프로퍼티를 파생 프로퍼티(derived property)라고 부르며 자주 사용한다.

var date: Date get() = Date(mills) set(value) { millis = value.time }

직렬화 문제 등으로 더이상 해당 타입을 사용할 수 없을 때 위와 같이 사용할 수 있다.

프로퍼티는 필드가 필요 없고 개념적으로 접근자(val은 getter, var은 getter, setter)를 나타낸다. 따라서 코틀린은 인터페이스에도 프로퍼티를 정의할 수 있다.

  • 인터페이스에 프로퍼티 정의

    interface Person { val name: String }
  • 프로퍼티 위임

    val db: Database by lazy { connectToDb() }
  • 확장 프로퍼티

    val Context.preferences: SharedPreferences get() = PreferenceManager.getDefaultSharedPreferences(this)

프로퍼티는 필드가 아니라 접근자를 나타낸다. 프로퍼티를 함수 대신 사용할 수도 있지만 완전히 대체해서 사용하는 것은 좋지 않다.

// BAD val Tree<Int>.sum: Int get() = when (this) { is Leaf -> value is Node -> left.sum + right.sum } // GOOD fun Tree<Int>.sum(): Int = when (this) { is Leaf -> value is Node -> left.sum() + right.sum() }

이런 게터에 재귀 형태의 많은 계산이 들어갈 것이라고 예상하지 않기 때문에 이러한 처리는 프로퍼티가 아닌 함수로 구현해야 한다.

원칙적으로 프로퍼티는 상태를 나타내거나 설정하기 위한 목적으로만 사용하는 것이 좋고 다른 로직 등을 포함하지 않아야 한다.

프로퍼티 사용 판단을 위한 간단한 질문

이 프로퍼티를 함수로 정의할 경우, 접두사로 get 또는 set을 붙일 것인가? 아니라면 프로퍼티로 만드는 것은 좋지 않다.

프로퍼티 대신 함수를 사용해야 하는 경우

  • 연산 비용이 높거나 복잡도가 O(1)보다 큰 경우
  • 비즈니스 로직을 포함하는 경우: 로깅 리스너 통지, 바인드된 요소 변경 등
  • 결정적이지 않은 경우: 같은 동작을 연속적으로 두번 했을 때 다른 값이 나올 수 있다면 함수 사용
  • 변환의 경우: 변환은 관습적으로 Int.toDouble()과 같은 변환 함수로 이루어지기 때문에 이런 변환을 프로퍼티로 만들면 오해를 불러 일으킬 수 있다.
  • 게터에서 프로퍼티의 상태 변경이 일어나야 하는 경우: 관습적으로 게터에서 프로퍼티의 상태 변화를 일으킨다고 생각하지 않는다.

Reference

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

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

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