ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [이펙티브 코틀린]가변성을 제한하라
    전공 도서 리뷰 2022. 6. 4. 00:36

    요소가 시간의 변화에 따라 변화하는 경우의 단점

    • 프로그램을 이해하고 디버깅하기 힘들어짐
    • 코드의 실행을 추론하기 어려워짐.
    • 멀티스레드 프로그래밍의 경우 동기화가 필요하다
      • 변경점이 많을 수록 충돌점이 많아진다.
    • 테스트하기 어렵다
      • 변경이 많을 수록 많은 조합을 테스트해야 한다.
    • 상태 변경을 다른 부분에 알려야 할 때도 있다.

    가변성 제한 방법

    • 읽기 전용 프로퍼티(val)
      • 완전히 변경 불가능하지 않다.
      • 다른 프로퍼티를 활용하는 사용자 정의 게터로도 정의 가능하다.
      • 정의 옆에 상태가 바로 적히므로 코드의 실행을 예측하는 것이 간단
      • 스마트 캐스트를 활용 가능
    • 가변 컬렉션과 읽기 전용 컬렉션 구분하기
      • 읽기 전용 컬렉션을 가변 컬렉션으로 다운캐스팅하면 안 된다.
      • 읽기 전용 컬렉션 사용 시 장점
        • 한 번 정의된 상태가 유지되므로 코드를 이해하기 쉽다
        • 공유했을 때도 충돌이 이뤄지지 않으므로 병렬 처리가 안전하다.
        • 객체에 대한 참조가 변경되지 않으므로 쉽게 캐시할 수 있다.
        • 방어적 복사본을 만들 필요가 없다.
          • 객체를 복사할 때 깊은 복사를 따로 하지 않아도 된다.
        • 다른 객체를 만들 때 활용하기 좋다.
        • 실행을 더 쉽게 예측 가능하다.
        • set 또는 map의 키로 사용 가능.
          • 키 값을 기반으로한 자료구조는 요소의 변경이 일어나면 찾을 수 없다.
    • 데이터 클래스의 copy
      • copy를 사용하여 immutable 객체를 수정할 수 있다.

    변경 가능 지점

    • mutable 컬렉션
      • ex)val list1 = MutableList<Int>
      • 리스트 구현 내부에 변경 가능 지점이 있다.
        • 멀티스레드 처리가 이뤄질 경우 동기화되어 있는지 확실히 알 수 없어 위험하다.
    • mutable 프로퍼티
      • ex)var list2 = List<Int>
      • 프로퍼티 자체가 변경 가능 지점.
        • 멀티스레드 처리의 안정성이 더 좋음.
        • 사용자 정의 세터를 활용하여 변경을 추적 가능.
        • Delegates.observable을 사용 시 변경 시에 로그를 출력할 수 있다.
        • 객체 변경을 제어하기가 더 쉽다.
    • 프로퍼티와 컬렉션 둘 다 mutable인 경우
      • 두 지점 모두에 대한 동기화를 구현해야 함.
      • 모호성이 발생하여 +=를 사용할 수 없다.

    변경 가능 지점 노출하지 말기

    • 상태를 나타내는 mutable 객체를 외부에 노출하는 것은 굉장히 위험하다.
      • 돌발적인 수정이 일어날 때 위험할 수 있다.
        • 이를 방지하기 위한 방법
          • 방어적 복제
            • 리턴되는 mutable 객체를 복제하는 것.
          • 읽기 전용 슈퍼클래스로 업캐스트하여 가변성을 제한

    실제 코드에 적용해본다.

    기존 ViewModel에서 사용되던 MutableStateFlow를 예로 들어보자.

    class HomeViewModel:ViewModel(){
    ...
    val nickname: MutableStateFlow<String> = MutableStateFlow("")
        val emoji: MutableStateFlow<String> = MutableStateFlow("")
        val weatherEmoji: MutableStateFlow<String> = MutableStateFlow("")
        val weatherDegree: MutableStateFlow<String> = MutableStateFlow("")
    

    각 프로퍼티들이 val로 정의되어 있으나, 사실상 안의 값은 수정이 가능하다.

    가변성을 가진 프로퍼티가 외부에 노출되어 있으므로, 캡슐화가 필요하다.

    ...
        private val _nickname = MutableStateFlow("")
        private val _emoji = MutableStateFlow("")
        private val _weatherEmoji = MutableStateFlow("")
        private val _weatherDegree = MutableStateFlow("")
        val nickname: StateFlow<String>
            get() = _nickname
        val emoji: StateFlow<String>
            get() = _emoji
        val weatherEmoji: StateFlow<String>
            get() = _weatherEmoji
        val weatherDegree: StateFlow<String>
            get() = _weatherDegree
    

    기존 MutableStateFlow 변수는 private 처리하여 외부에서는 접근을 차단하고,

    ViewModel 내부에서는 자유롭게 수정가능하도록 한다.

    그리고 외부에 노출할 용도로 MutableStateFlow를 업캐스팅하여 수정 불가능하도록 StateFlow 변수를 getter로 정의한다.

    이로써 값은 외부에 노출할 수 있으나, 내부의 변수를 수정하지 못하도록 캡슐화를 진행하였다.

Designed by Tistory.