일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 스택 자바 코드
- 클래스
- 2022 플러터 안드로이드 스튜디오
- 안드로이드 유닛테스트란
- 안드로이드 os 구조
- rxjava disposable
- 안드로이드 라이선스
- 객체
- 안드로이드 유닛 테스트 예시
- 안드로이드 라이선스 종류
- 큐 자바 코드
- 서비스 쓰레드 차이
- 안드로이드 유닛 테스트
- 서비스 vs 쓰레드
- rxjava cold observable
- 스택 큐 차이
- android retrofit login
- jvm 작동 원리
- 안드로이드 레트로핏 사용법
- 2022 플러터 설치
- 멤버변수
- 플러터 설치 2022
- Rxjava Observable
- jvm이란
- 안드로이드 레트로핏 crud
- rxjava hot observable
- ar vr 차이
- android ar 개발
- ANR이란
- 자바 다형성
- Today
- Total
나만을 위한 블로그
[이펙티브 코틀린] 아이템 30. 요소의 가시성을 최소화하라 본문
작은 인터페이스는 배우기 쉽고 유지하기 쉽다. 기능이 많은 클래스보단 적은 클래스를 이해하는 게 쉽고 유지보수하기도 쉽다. 일반적으로 어떤 수정을 가하기 위해선 클래스 전체를 이해하고 있어야 한다. 보이는 요소 자체가 적다면 유지보수하고 테스트할 게 적다.
변경을 가할 때는 기존의 것을 숨기는 것보다 새로운 것을 노출하는 게 쉽다. 일반적으로 공개적으로 노출된 요소들은 공개 API의 일부고 외부에서 사용할 수 있다. 따라서 이런 요소들은 이미 외부에서 쓰이고 있을 것이다. 그래서 이런 요소들을 변경하면 이 코드를 쓰는 모든 부분이 영향을 받는다. 가시성 관련 제한을 변경하는 건 더 어렵다. 따라서 처음에는 작은 API로서 개발하도록 강제하는 것이 더 좋을 수 있다.
클래스의 상태를 나타내는 프로퍼티를 외부에서 바꿀 수 있다면 클래스는 자신의 상태를 보장할 수 없다. 클래스가 만족해야 하는 클래스의 상태에 대한 규약 등이 있을 수 있다. 이런 규약을 모르는 사람은 클래스 상태를 마음대로 바꿀 수 있으므로 클래스 불변성이 무너질 가능성이 있다. 이전에 봤던 CounterSet을 다시 본다. 이 코드에서 elementsAdded의 setter를 private로 설정했었다. 이 부분이 없다면 외부에서 이 코드를 강제로 바꿀 수 있고 이 프로퍼티의 신뢰성에 문제가 생길 수 있다. setter만 private로 바꾸는 코드는 굉장히 많이 사용된다.
class CounterSet<T>(
private val innerSet: MutableSet<T> = mutableSetOf()
): MutableSet<T> by innerSet {
var elementsAdded: Int = 0
private set
override fun add(element: T): Boolean {
elementsAdded++
return innerSet.add(element)
}
override fun addAll(elements: Collection<T>): Boolean {
elementsAdded += elements.size
return innerSet.addAll(elements)
}
}
일반적으로 코틀린에선 구체 접근자의 가시성을 제한해서 모든 프로퍼티를 캡슐화하는 게 좋다. 서로 의존하는 프로퍼티가 있을 때는 객체 상태를 보호하는 게 더 중요해진다. 예를 들어 mutableLazy 델리게이트를 구현할 때를 생각해 보라. 이는 initialized가 true면 값 초기화가 이뤄지고 이 때의 값은 T 타입이란 걸 예상할 수 있다. 이 때 initialized의 setter가 노출되선 안 된다. 이게 노출되면 예상못한 변경에 의해 예외가 발생하고 코드 신뢰성이 떨어질 수 있기 때문이다.
class MutableLazyHolder<T>(val initializer: () -> T) {
private var value: Any = Any()
private var initialized = false
override fun get(): T {
if (!initialized) {
value = initializer()
initialized = true
}
return value as T
}
override fun setValue() {
this.value = value
initialized = true
}
}
가시성이 제한될수록 클래스 변경을 쉽게 추적할 수 있으며 프로퍼티 상태를 더 쉽게 이해할 수 있다. 이는 동시성을 처리할 때 중요하다. 상태 변경은 병렬 프로그래밍에서 문제가 된다. 따라서 많은 걸 제한할수록 병렬 프로그래밍을 할 때 안전해진다.
가시성 한정자 사용하기
내부 변경 없이 작은 인터페이스를 유지하고 싶다면 가시성을 제한하면 된다. 기본적으로 클래스, 요소를 외부에 노출할 필요가 없다면 가시성을 제한해서 외부에서 접근할 수 없게 만드는 게 좋다.
클래스 멤버의 경우 4개의 가시성 한정자를 쓸 수 있다.
- public(디폴트) : 어디서나 볼 수 있음
- private : 클래스 안에서만 볼 수 있음
- protected : 클래스, 서브클래스 내부에서만 볼 수 있음
- internal : 모듈 내부에서만 볼 수 있다
톱레벨 요소에선 3가지 가시성 한정자를 쓸 수 있다.
- public(디폴트) : 어디서나 볼 수 있음
- private : 같은 파일 안에서만 볼 수 있음
- internal : 모듈 내부에서만 볼 수 있음
모듈과 패키지를 혼동하는 개발자들이 있는데 의미가 전혀 다르다. 코틀린에서 모듈이란 함께 컴파일되는 코틀린 소스를 의미한다. 모듈이 다른 모듈에 의해 사용될 가능성이 있다면 internal을 써서 공개하고 싶지 않은 요소를 숨긴다.
요소가 상속을 위해 설계됐고 클래스, 서브클래스에서만 쓰게 만들고 싶으면 protected를 쓴다. 동일한 파일 또는 클래스에서만 요소를 쓰게 하고 싶다면 private을 쓴다. 코틀린은 지역적으로만 쓰이고 있는 요소는 private로 쓰는 게 좋다는 컨벤션을 제공해준다.
이런 규칙은 데이터를 저장하도록 설계된 클래스(데이터 모델 클래스, DTO)에는 적용하지 않는 게 좋다. 데이터를 저장하게 설계된 클래스는 숨길 이유가 없기 때문이다. 따라서 프로퍼티를 쓸 수 있게 눈에 띄게 만드는 게 좋으며 안 필요하면 그냥 제거하는 게 좋다.
class User(
val name: String,
val surName: String,
val age: Int
)
한 가지 제한은 API 상속 시 오버라이드해서 가시성을 제한할 수는 없다. 이는 서브클래스가 슈퍼클래스로도 쓰일 수 있기 때문이다. 이게 상속보다 컴포지션을 선호하는 대표적인 이유다.
'책 > Effective Kotlin' 카테고리의 다른 글
[이펙티브 코틀린] 아이템 32. 추상화 규약을 지켜라 (0) | 2023.01.28 |
---|---|
[이펙티브 코틀린] 아이템 31. 문서로 규약을 정의하라 (0) | 2023.01.25 |
[이펙티브 코틀린] 아이템 29. 외부 API를 랩(wrap)해서 사용하라 (0) | 2023.01.14 |
[이펙티브 코틀린] 아이템 28. API 안정성을 확인하라 (0) | 2023.01.13 |
[이펙티브 코틀린] 아이템 27. 변화로부터 코드를 보호하려면 추상화를 사용하라 (0) | 2023.01.09 |