최데브는 오늘도 프로그래밍을 한다.

[이펙티브 코틀린] 1. 가변성을 제한하라 본문

Effective Kotiln

[이펙티브 코틀린] 1. 가변성을 제한하라

최데브 2023. 1. 8. 23:48
반응형

코틀린의 요소 중 일부는 상태를 가진다.

상태라는건 상황에 따라 값이 변화할 수 있는 것을 의미한다.

대표적으로 var 프로퍼티와 mutable 객체가 있다.

 

시간 변화에 따라 변하는 요소를 표현할 수 있는것은 유용하지만 여러가지 고려할 점이 생긴다.

 

1. 프로그램을 이해하고 상태 추척하는것이 어려워진다.

2. 가변성을 가지고 있으면 코드의 진행을 추론하기 어려워진다.

3. 멀티스레드 일때 동기화의 문제를 만날 수 있다.

4. 테스트하기가 어려워진다. 변경되는 값이 많을수록 여러 케이스를 테스트 해야한다.

5. 상태 변경이 일어나면 이 상태를 사용하고 있는 곳에 지속적으로 알려주는 번거로움이 생긴다.

 

예를 들면 이런 상황이다.

 

suspend fun main() {
    val lock = Any()
    var name =0
    coroutineScope {
        for(i in 1.. 1000){
            launch {
                delay(10)
                name +=1
            }
        }
    }
    print(name)
}

위 코드는 var 프로퍼티를 이용하는 name 때문에 동기화를 해주지 않으면 실행할떄마다

다른 값이 나온다.

suspend fun main() {
    val lock = Any()
    var name =0
    coroutineScope {
        for(i in 1.. 1000){
            launch {
                delay(10)
                synchronized(lock){
                    name +=1
                }
            }
        }
    }
    print(name)
}

 

 

그래서 원하는대로 동작하기 위해서는 synchronized 같은 동기화 작업을 직접 해줘야 문제가 발생하지 않는다.

이런 경우는 간단하지만 프로그램이 복잡해지면 개발자가 신경써줘야 하는 부분이 많아지는 것이다.

 

그럼 우리는 어떻게 하는게 좋을까?

크게 3가지 파트로 소개할 수 있다.

1. 코틀린에서 가변성 제한하기

2.다른 종류의 변경가능지점 컨트롤

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

 

코틀린에서 가변성 제한

코틀린에서는 가변성 제한을 위해 이것저것 제공해주는데

 

1. val

2. 데이터 클래스의 copy

3. 가변 컬렉션의 읽기 전용 컬렉션 구분

 

- val

 

읽기 전용 프로퍼티인 val 은 일반적인 방법으로는 값이 바뀌지 않는다.

그러나 절대 값을 바꿀 수 없다라고 생각해서는 안된다.

 

val list = mutableListOf(1,2,3)

list.add(4)

이렇게 값이 바뀌기도 한다.

list = mutableListOf(1,2,3,4)

이런식으로 재할당이 불가능 하다는것이지 불변하는 값은 아니다.

 

val 은 은 변경 될수도 있지만 재할당이 불가능해서 동기화 문제등을 줄일 수 있다.

완전하게 불편하게 하고 싶다면 앞에 final 을 붙이면 된다.

 

- copy

 

불변객체를 사용하는것이 안정성에 좋으나 변경 상태를 꼭 만들어야하는 상황이라면 불변 객체를 만들고

이를 copy 해서 사용하자.

 

- 가변 컬렉션의 읽기 전용 컬렉션 구분

변경할 수 있는 컬렉션과 읽기 전용만을 위한 컬렉션은 구분되어서 코틀린에서 제공하고 있다.

코틀린에서는 읽기 전용으로 설계된 컬렉션도 내부적으로는 진짜로 불변하지는 않게 만들어져있다.

내부적으로 불변하지 않는 컬렉션을 외부적으로 불변하게 보이도록 만들어서 안정성을 확보하기 위함인데 문제는

이를 개발자가 다운캐스팅 하거나 언어에서 의도지 않은 쪽으로 일부러 바꾼다면 당장은 작동을 잘 할지 몰라도

언제 문제가 발생하게 될지 모르는 위험한 코드가 된다.

 

예를 들면 

var list = listOf(1,2,3)

if(list is MutableList){
	list.add(4)
}

이렇게 하지말라는 뜻이다.

 

2 . 다른 종류의 변경가능지점 컨트롤

 

예를 들어서 변경할 수 있는 리스트를 만들어야한다면 우리는

val listA : MutableList<Int> = mutableListof()

var listB : List<Int> = listOf() 

라는 두가지 선택지를 떠올릴 수 있다.

 

둘다 변경은 가능하지만 안을 들여다보면 변경이 되는 방법이 다르다.

listA.add(1)

listB = listB+1

 

이 부분이 핵심이다.

첫번째 줄은 리스트 구현 내부에 변경 가능 지점이 있어서 멀티스레드 처리가 될때는 적절한 동기화가 됐는지 확실히 알 수 없어서 위험하다. 그러나 두번째 줄의 경우는 프로퍼티 자체가 변경 가능한 지점이기 떄문에 멀티스레드 관점에서는 좀 더 안전하다고 할 수 있다

 

3, 변경 가능 지점 노출 하지않기

 

mutable 객체를 외부에 노출하는것은 위험하다.

함수의 리턴값으로 노출되게 된다거나 하는 상황에서는 data 한정자로 만들어지는 copy 를 사용해서 방어적 복제를 하면 원본에 영향을 주지않고 데이터를 사용할 수 있다.

반응형
Comments