변수의 변경 가능 지점을 최소화 해야 하는 이유
프로그래밍을 하다보면, 이곳 저곳에서 변수의 값이 변화되는 것을 볼 수 있다. 코드의 양이 작을 때는 이런 것이 문제가 안되지만 코드의 양이 많아질 수록 문제가 된다. 특히 특정 클래스에 속한 변수가 외부에서 직접 접근된 다음 수정되면 해당 클래스 상태(State)의 변경 가능 지점이 늘어나기 때문에 문제가 생긴다.
예를 들어 ExampleView와 ExampleView의 데이터를 저장하는 ExampleViewModel이 있고 ExampleViewModel의 viewData는 서버에서 가져온다고 해보자. 이때 viewData는 초기화 시 한 번만 가져오고 이후에 바뀌면 안된다. 그러면 다음과 같이 코드가 만들어질 수 있다.
class ExampleView(){
val viewModel = ExampleViewModel()
}
class ExampleViewModel(){
var viewData : String = viewData = fetchFromServer()
}
하지만 위의 코드는 안정성이 떨어진다. viewData가 ExampleView에서 접근이 가능할 뿐만 아니라 수정 또한 가능하기 때문이다.
예를 들어 ExampleView에 changeData라는 메서드를 넣고 viewData를 수정하도록 하면 수정이 되어버린다.
class ExampleView(){
val viewModel = ExampleViewModel()
fun changeData(){
viewModel.viewData = "changed data"
}
}
이렇게 되면 viewData는 바뀌면 안됨에도 불구하고 외부 클래스에서 수정이 가능해져 버린다. 만약 이 코드가 작다면 문제가 안되지만 여러 클래스에서 이 값을 위와 같이 수정할 수 있게 만들어버리면 코드 어디에서 오류가 날지 알 수 없기 때문에 안정성이 매우 떨어진다.
변수의 변경 가능 지점 최소화 전략
아주 간단한 방법으로 외부에서 조회만 가능하고 변경 불가능하게 만들 수 있다. 바로 var 대신 val을 사용하는 방법이다. val은 변수에 할당된 객체의 주소값을 바꾸지 못하도록 만들어 변수가 특정 객체만을 바라보도록 만들 수 있다.
class ExampleView(){
val viewModel = ExampleViewModel()
fun changeData(){
viewModel.viewData = "changed data" // 오류 발생!!
}
}
class ExampleViewModel(){
val viewData : String = fetchFromServer()
}
하지만 이 방법은 문제가 있다 바로 ExampleViewModel 내부에서도 viewData에 할당된 변수의 값을 바꾸지 못한다는 것이다.
이를 해결하기 위해서는 가변성이 필요한 내부에서는 가변성 있게 만들고 외부에 노출되는 데이터는 불가변적으로 만들어야 한다. 이를 위해서 내부에서는 var로 변수를 만들고 외부에는 이 변수를 가져올 수 있는 메서드를 노출하면 된다.
class ExampleViewModel(){
private var _viewData : String = fetchFromServer()
fun getViewData() : String = _viewData
}
이 방법을 사용하면 ExampleViewModel 외부에서 데이터 조회가 getViewData 메서드를 통해 가능하고 변경은 불가능해진다. 또한 ExampleViewModel 내부에서는 _viewData의 조회와 변경이 모두 가능해진다.
하지만 여전히 field의 getter가 외부 메서드로 빠져 있는 것은 코드상 깔끔하지 않다. 이를 private setter을 사용해 한 번 더 개선하면 다음과 같이 사용할 수 있다.
class ExampleViewModel() {
var viewData: String = ""
private set
}
이렇게 하면 외부에서는 조회가 가능하지만 데이터 변경은 클래스 내부에서만 가능해지는, 변경가능 지점이 최소화되는 변수가 만들어진다.
안드로이드의 변경 가능 지점 최소화 전략
안드로이드에서는 MutableState나 MutableStateFlow를 만들고 외부에는 State, StateFlow를 노출시키는 방법이 많이 사용된다.
class ExampleViewModel(){
private val _viewData : MutableState<String> = mutableStateOf(fetchFromServer())
val viewData : State<String> = _viewData
}