Kotlin

[EffectiveKotlin-Item02] 변수의 스코프를 최소화 해라

dev_roach 2022. 5. 29. 12:06
728x90

변수의 스코프를 최소화 해라

만약 loop 안에서만 변수가 쓰인다면 loop 안으로 변수를 넣어서 scope 를 최소화 해라.
보통의 언어 에서 변수의 scope 는 보통 curly braces 안으로 생성지만, Kotlin 에서는 외부의 변수에도 접근 가능하다.
음, 내가 생각해도 뭔가 잘 읽기 힘들게 적은거 같아 코드로 적어보려고 한다.

val a = 1

fun fizz() {
    val b = 2
    print(a+b)
}

val buzz = {
    val c = 3
    println(a + c)
}

위의 예시를 보면, buzz 와 fizz 의 curly braces 지역 scope 에서 외부 변수인 a 에 접근할 수 있음을 알 수 있다.
반대로 모두 알고 있겠지만 Outer area 에서 inner function scope 의 variable 에 접근은 불가능하다. 가시성을 제한하는 예시를 한번 알아보자.

val user: User
for (i in users.indices) {
    user = users[i]
    println("User at $i is $user")
}

위의 예시를 보면 user 는 for function scope 안에서만 이용하므로 for scope 안으로 변수 scope 를 제한시켜야 한다.
따라서 아래와 같이 코드를 작성하는 것이 좀 더 좋은 방향일 것 이다.

for ((i, user) in users.withIndex()) {
    println("User at $i is $user")    
}

위와 같이 코드를 작성했을때 user 는 for loop 의 scope 로 제한되므로 outside 에서 접근하는 것이 불가능 해진다.
이렇게 변수의 scope 를 tight 하게 제한했을때, 우리는 프로그램을 좀더 쉽게 추적할 수 있고, 관리할 수 있다고 한다.
이렇게 변수의 scope 를 제한하는것 또한 property 가 어느 지점에서 수정되는지를 제한할 수 있고, 추적/관리하기 쉬우므로 immutable properties 로 변수를 제한하는것과 비슷한 의미이다. 아래 코드를 한번 보자

val user: User
if (hasValue) {
    user = getValue()
} else {
    user = User()
}

위의 코드를 어떻게 scope 를 제한할 수 있을까?

val user: User = if (hasValue) {
    getValue()
} else {
    User()
}

위와 같이 적는것이 value 가 필요한 시점이 Initialize 를 하는 것이므로 좀 더 좋은 코드일 것이다.
우리가 한번에 두개 이상의 properties 를 update 해야 한다면 destructuring 을 이용할 수도 있다.

fun updateWeather(degress: Int) {
    val description: String
    val color: Int
    if (degress < 5) {
        description = "cold"
        color = Color.BLUE
    } else if (degress < 23) {
        description = "mild"
        color = Color.YELLOW
    } else {
        description = "hot"
        color = Color.RED
    }
}


fun updateWeather(degress: Int) {
    val (description, color) = when {
        degress < 5 -> "cold" to Color.BLUE
        degress < 23 -> "mild" to Color.YELLOW
        else -> "hot" to Color.RED
    }
}

destructuring 과 Pair 를 통해서 multiplation properties 를 깔끔하게 update 를 할 수 있다는게 좋은것 같다. 좀 더 Kotlin 스러운 느낌

Capturing

저자의 Kotlin Coroutines 를 이용해 에라토스테네스의 체를 구하는 로직을 한번 보자.

val numbers = (2..100).toList()
val primes = mutableListOf<Int>()
while (numbers.isNotEmpty() {
    val prime = numbers.first()
    primes.add(prime)
    numbers = number.filter { it % prime != 0 }
})

val primes: Sequence<Int> = sequence {
    val numbers = generateSequence(2) {it + 1}

    while (true) {
        val prime = numbers.first()
        yield(prime)
        numbers = numbers.drop(1).filter( it % prime != 0 )
    }
}

print(primes.take(10).toList()) [2, 3, 5, 7, 11, 13, 17, 19, 23 ,29]

Sequence 를 이용해서 에라토스테네스체를 optimize 한 과정이다. 아래 코드는 위와 비슷해보이지만 제대로 동작하지 않는다.

val primes: Sequence<Int> = sequence {
    val numbers = generateSequence(2) {it + 1}

    var prime: Int
    while (true) {
        prime = numbers.first()
        yield(prime)
        numbers = numbers.drop(1).filter( it % prime != 0 )
    }
}

print(primes.take(10).toList()) [2, 3, 5, 6, 7, 8, 9, 10, 11, 12]

왜 var prime 을 while loop 위로 올렸다고 다르게 동작할까? 대부분의 사람들은 같게 동작 한다고 생각할 것이다.
sequence 는 기본적으로 executed lazy 한 속성을 지니고 있다. 즉, filter 가 작동되기도 전에 prime 의 값이 바뀌어서 저장될 수 있다는 뜻이다. 따라서 이런 문제가 있을 수 있기때문에 scope 를 한정짓고, immutable 하게 property 를 이용하라는 것이다.

728x90

'Kotlin' 카테고리의 다른 글

코루틴으로 Heap 사용량 최적화(?) 한 이야기 끄적  (0) 2022.06.02
Kotlin Coroutines - Basic  (0) 2022.05.29
Effective Kotlin - Item01  (0) 2022.05.25
GC 분석해보기  (0) 2022.05.24
Kotlin Delegation  (0) 2022.05.16