일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 자바 다형성
- 클래스
- 멤버변수
- 안드로이드 레트로핏 사용법
- 안드로이드 라이선스 종류
- android retrofit login
- 안드로이드 유닛테스트란
- rxjava hot observable
- 안드로이드 유닛 테스트
- 스택 큐 차이
- 안드로이드 os 구조
- 스택 자바 코드
- Rxjava Observable
- 플러터 설치 2022
- ar vr 차이
- 안드로이드 유닛 테스트 예시
- 2022 플러터 안드로이드 스튜디오
- jvm 작동 원리
- jvm이란
- 객체
- 서비스 쓰레드 차이
- 안드로이드 레트로핏 crud
- 서비스 vs 쓰레드
- 2022 플러터 설치
- 큐 자바 코드
- 안드로이드 라이선스
- ANR이란
- rxjava cold observable
- rxjava disposable
- android ar 개발
- Today
- Total
나만을 위한 블로그
[이펙티브 코틀린] 아이템 1. 가변성을 제한하라 본문
모듈 : 클래스, 객체, 함수, 타입 별칭(typealias), Top-level property 등 여러 요소로 구성됨
타입 별칭?
https://kotlinlang.org/docs/type-aliases.html
코틀린은 모듈로 프로그램을 설계한다.
요소 중 일부는 상태(state)를 가질 수 있다. 읽고 쓸 수 있는 var 프로퍼티를 쓰거나 mutable 객체를 쓰면 상태를 가질 수 있다.
상태를 갖게 하는 건 양날의 검이다. 시간 변화에 따라 변하는 요소를 표현할 수 있다는 건 유용하지만 상태를 적절하게 관리하는 게 어렵다.
- 프로그램 이해, 디버그가 힘들어짐 : 상태 변경이 많아지면 추적이 힘들다 -> 이런 클래스는 이해하기 어렵고 코드 수정하기도 힘들다 -> 클래스가 예상못한 상황 or 오류를 발생시키는 경우 문제가 된다
- 가변성(mutability)이 있으면 코드 실행 추론이 어렵다 : 현재 어떤 값을 갖고 있는지 알아야 코드 실행을 예측할 수 있다. 또한 한 시점에 확인한 값이 계속 동일하게 유지된다고 확신할 수 없다
- 멀티쓰레드 프로그램일 때는 적절한 동기화가 필요하다
- 테스트가 어렵다
- 상태 변경이 일어날 때 이런 변경을 다른 부분에 알려야 하는 경우가 있다
가변성은 시스템의 상태를 나타내기 위한 중요한 방법이다. 하지만 변경이 일어나야 하는 부분을 신중하고 확실하게 결정하고 사용해야 한다.
코틀린에서 가변성 제한하기
코틀린은 가변성을 제한할 수 있게 설계됐다 -> 불변 객체 생성, 프로퍼티 변경을 막는 것이 쉽다. 이걸 위한 방법이 많은데 그 중 가장 많이 쓰이는 중요한 것들
- 읽기 전용 프로퍼티 val
- 가변 컬렉션, 읽기 전용 컬렉션 구분하기
- data class의 copy()
읽기 전용 프로퍼티 val
val을 써서 읽기 전용 프로퍼티를 만들 수 있다. 값(value)처럼 동작하고 일반적인 방법으론 값이 변하지 않는다. 읽고 쓸 수 있는 프로퍼티는 var로 만든다.
읽기 전용 프로퍼티가 mutable 객체를 담고 있다면 내부적으로 변할 수 있다.
fun main() {
val list = mutableListOf(1, 2, 3)
list.add(4)
print(list)
}
- 읽기 전용 프로퍼티가 완전히 변경 불가능한 것은 아니다
- 읽기 전용 프로퍼티는 다른 프로퍼티를 활용하는 커스텀 getter로도 정의 가능하다. var 프로퍼티를 쓰는 val 프로퍼티는 var 프로퍼티가 변할 때 변할 수 있다
코틀린 프로퍼티는 기본적으로 캡슐화되어 있고 사용자 정의 접근자(getter, setter)를 가질 수 있다 -> API 변경, 정의 시 유연하다
- var : getter, setter 모두 제공
- val : 변경할 수 없기 때문에 getter만 제공 -> var로 오버라이드 가능
val의 값은 바뀔 수 있지만 프로퍼티 레퍼런스 자체를 바꿀 수는 없어서 동기화 문제 등을 줄일 수 있다. 그래서 일반적으로 val을 많이 쓴다.
val은 읽기 전용 프로퍼티지만 불변을 의미하진 않는다. 완전히 변경할 필요가 없으면 final 프로퍼티를 쓰는 게 좋다.
가변 컬렉션과 읽기 전용 컬렉션 구분하기
- 읽기 전용 : Iterable, Collection, Set, List 인터페이스
- 읽고 쓰기 가능 : MutableIterable, MutableCollection, MutableSet, MutableList 인터페이스
Mutable이 붙은 인터페이스는 대응되는 읽기 전용 인터페이스를 상속받아서 변경을 위한 메서드를 추가한 것이다.
읽기 전용 컬렉션이 내부 값을 변경할 수 있다는 의미는 아니다. 대부분 변경 가능하지만 읽기 전용 인터페이스가 이를 지원하지 않아서 변경할 수 없다.
List를 읽기 전용으로 리턴하면 이걸 읽기 전용으로만 써야 한다. 읽기 전용에서 mutable로 바꿔야 한다면 복제(copy())를 통해서 새 mutable 컬렉션을 만드는 list.toMutableList()를 써야 한다.
fun main() {
val list = listOf(1, 2, 3)
// list.add(4) // 컴파일 에러 발생
val mutableList = list.toMutableList()
mutableList.add(4)
}
data class의 copy()
String, Int처럼 내부적인 상태를 바꾸지 않는 불변 객체를 많이 쓰는 데는 이유가 있다. 불변 객체를 쓰면 아래의 장점이 있다.
- 한 번 정의된 상태가 유지됨 -> 코드 이해가 쉬움
- 불변 객체는 공유했을 때도 충돌이 따로 이뤄지지 않음 -> 안전한 병렬 처리 가능
- 불변 객체에 대한 참조는 변경되지 않음 -> 캐시 쉬움
- 불변 객체는 방어적 복사본(defensive copy)을 만들 필요가 없다. 또한 객체 복사 시 깊은 복사를 따로 하지 않아도 된다.
- 불변 객체는 다른 객체를 만들 때 활용하기 좋다. 또한 실행을 더 쉽게 예측할 수 있다.
- 불변 객체는 Set 또는 Map의 key로 쓸 수 있다. Set, Map이 내부적으로 해시 테이블을 쓰고 해시 테이블은 첫 요소를 넣을 때 요소의 값을 기반으로 버킷을 결정하기 때문이다. 수정이 발생하면 해시 테이블 안에서 요소를 못 찾게 된다
- mutable 객체 : 예측하기 어렵고 위험하다
- immutable 객체 : 변경할 수 없다 -> 자신의 일부를 수정한 새 객체를 만드는 메서드를 가져야 한다
ex) Int는 불변이다 -> 내부적으로 plus(), minus()로 자신을 수정한 새로운 Int를 리턴할 수 있다
ex2) Iterable은 불변이다 -> map(), filter()로 자신을 수정한 새로운 Iterable 객체를 만들어 리턴한다
예를 들어 User 불변 객체가 있고 성(surName)을 바꿔야 한다면 withSurname()같은 메서드를 제공해서 자신을 수정한 새로운 객체를 만들 수 있게 해야 한다.
fun main() {
data class User(
val name: String,
val surName: String
) {
fun withSurname(surName: String) = User(name, surName)
}
var user = User("Maja", "Markiewicz")
user = user.withSurname("Moskala")
print(user)
}
모든 프로퍼티를 대상으로 이런 함수를 만드는 건 귀찮다. 이 때 data 한정자를 사용한다. data 한정자는 copy()를 만들어주는데 이걸 쓰면 모든 기본 생성자 프로퍼티가 같은 새로운 객체를 만들 수 있다.
fun main() {
data class User(
val name: String,
val surName: String
)
var user = User("Maja", "Markiewicz")
user = user.copy(surName = "Moskala")
print(user)
}
변경할 수 있다는 측면만 보면 mutable 객체가 더 좋아 보이지만, 데이터 모델 클래스를 만들어 immutable(불변) 객체로 만드는 게 더 많은 장점을 가지기 때문에 기본적으로 이렇게 만드는 게 좋다.
다른 종류의 변경 가능한 지점
변경 가능한 리스트를 만들어야 할 때 2가지 선택지가 있다.
- mutable collection 생성
- var로 읽고 쓸 수 있는 프로퍼티 생성
둘 다 변경 가능하지만 그 방법이 다르고 실질적으로 이뤄지는 처리가 다르다.
mutable list 대신 mutable 프로퍼티를 쓰는 형태는 사용자 정의 setter(또는 이걸 쓰는 delegate)를 써서 변경을 추적할 수 있다.
최악의 방식은 프로퍼티, 컬렉션을 모두 변경 가능한 지점으로 만드는 것이다.
// 이렇게 하지 마라
val list3 = mutableListOf<Int>()
변경 가능 지점 노출하지 말기
상태를 나타내는 mutable 객체를 외부에 노출하는 건 굉장히 위험하다. 돌발적인 수정이 일어날 때 위험할 수 있다.
이걸 처리하는 방법은 2가지다.
- 리턴되는 mutable 객체를 복제하는 것 = 방어적 복제(defensive copying)
- 가능하다면 무조건 가변성을 제한한다. 컬렉션은 객체를 읽기 전용 슈퍼타입으로 업캐스트해서 가변성을 제한할 수도 있다
'책 > Effective Kotlin' 카테고리의 다른 글
[이펙티브 코틀린] 아이템 6. 사용자 정의 오류보다는 표준 오류를 사용하라 (0) | 2022.05.26 |
---|---|
[이펙티브 코틀린] 아이템 5. 예외를 활용해 코드에 제한을 걸어라 (0) | 2022.05.22 |
[이펙티브 코틀린] 아이템 4. inferred 타입으로 리턴하지 마라 (0) | 2022.05.15 |
[이펙티브 코틀린] 아이템 3. 최대한 플랫폼 타입을 사용하지 마라 (0) | 2022.05.08 |
[이펙티브 코틀린] 아이템 2. 변수의 스코프를 최소화하라 (0) | 2022.05.07 |