notepad by Oxix
안정성1-1 가변성을 제한하라 본문
코틀린은 모듈로 프로그램을 설계한다. 모듈은 클래스, 객체, 함수, 타입 별칭, 톱레벨 프로퍼티 등 다양한 요소로 구성된다. var, mutable 객체를 사용할 경우 상태를 가질 수 있다.
이처럼 요소가 상태를 갖는 경우, 해당 요소의 동작은 사용 방법 및 이력에도 의존하게 된다. 시간의 변화에 따라 요소를 표현하는 것은 유용하지만, 상태를 적절하게 관리하는 건 어렵다. 공유 상태를 관리하는것이 얼마나 힘든 일인지 간단한 예로 확인해보자
fun main() {
var num = 0
for (i in 1..1000) {
thread {
Thread.sleep(10)
num+=1
}
}
Thread.sleep(5000)
println(num)
}
위 코드를 멀티스레드를 활용하여 프로퍼티를 수정하면 충돌에 의해 일부 연산이 이루어지지 않는다. 상태를 관리하기 힘든 이유는 다음과 같다.
1. 프로그램을 이해하혹 디버그 하기 힘들다. 상태를 갖는 부분들의 관계를 이해해야 하며, 상태 변경이 많아질 수록 추적하는 것이 힘들어진다. 즉, 유지보수에 문제가 생길 확률이 크다
2. 가변성이 있으면 코드의 실행을 추론하기가 어려워진다. 시점에 따라 값이 달라질 수 있으므로 현재 값이 얼마인지 알아야 코드의 실행을 예측하고, 동일한 값이 유지된다고 확신하기 어렵다.
3. 멀티스레드 프로그램일 경우 적절한 동기화가 필요하다. 변경이 일어나는 모든 부분에서 충돌이 발생할 수 있다. 위의 경우가 그렇다.
4. 상태 변경이 일어날 때 다른 부분에 알려야 할 경우가 있다. 예를 들면 리스트에 가변 요소를 추가한다면 변경이 일어날 때 마다 리스트 전체를 다시 정렬해야 한다.
가변성은 생각보다 단점이 많아 이를 완전하게 제어하는 순수 함수형 언어도 있지만 가변성에 너무 많은 제한이 걸려 프로그램을 작성하기가 굉장히 어렵다. 가변성은 시스템의 상태를 나타내기 위한 중요한 방법 중 하나이다. 하지만 변경이 일어나야 하는 부분을 신중하고 확실하게 결정하고 사용해야 한다.
코틀린에서 가변성 제한 하기
코틀린은 가변성을 제한 할 수 있는 방법은 다음과 같다.
- 읽기 전용 프로퍼티 val
- 가변 컬렉션과 읽기 전용 컬렉션 구분하기
- 데이터 클래스의 copy
읽기 전용 프로퍼티(val)
val를 사용하여 읽기 전용 프로퍼티를 만들 수 있다. 마치 값 처럼 도앚ㄱ하며, 일반적인 방법으로는 값이 변하지 않는다. 그렇다고 읽기 전용 프로퍼티가 완전히 변경 불가능 한 것은 아니다. 읽기 전용 프로퍼티가 mutable 객체를 담고 있다면 내부적으로 변할 수 있다.
var firstName = "Chan"
var lastName = "LEE"
val fullName: String get() = "$firstName $lastName"
fun main() {
println(fullName)
firstName = "Jong Chan"
println(fullName)
}
코틀린의 프로퍼티는 기본적으로 캡슐화되어 있고, 추가적으로 사용자 정의를 가질 수 있다. 이러한 특성으로 코틀린은 API를 변겨하거나 정의할 때 굉장히 유연하다. 추가적으로 var은 setter를 제공하지만 val은 그렇지 않다. val은 일기 전용 프로퍼티지만, 변경할 수 없음을 의미하는 것은 아니라는 것을 기억해야 한다. 또한 게터, 델리게이트로 정의할 수 있고 완전히 변경할 필요가 없다면 final 프로퍼티를 사용하는 것이 좋다. val은 정의 옆에 상태가 바로 적히므로 코드의 실행을 예측하는 것이 훨씬 간단하다.
가변 컬렉션과 읽기 전용 컬렉션 구분하기
읽기 전용 컬렉션이 내부의 값을 변경할 수 없다는 의미는 아니다. 그렇지만 해당 컬렉션을 진짜 불변하게 만들지 않고, 불변하게 보이게 만들어서 얻어지는 안정성이다. 하지만 개발자가 다운캐스팅을 할 때 문제가 발생한다. 예를 들어 리스트를 읽기 전용으로 리턴하면, 이를 읽기 전용으로만 사용해야 한다. 이는 단순한 계약의 문제라고 할 수 있다. 이러한 코드는 안전하지 않고 예측하지 못한 결과를 초래한다.