관리 메뉴

나만을 위한 블로그

[이펙티브 코틀린] 아이템 2. 변수의 스코프를 최소화하라 본문

책/Effective Kotlin

[이펙티브 코틀린] 아이템 2. 변수의 스코프를 최소화하라

참깨빵위에참깨빵_ 2022. 5. 7. 23:26
728x90
반응형

상태를 정의할 때는 변수와 프로퍼티의 스코프를 최소화하는 게 좋다.

 

  • 프로퍼티보다는 지역 변수를 쓰는 게 좋다
  • 최대한 좁은 스코프를 갖게 변수를 사용한다. 예를 들어 반복문 안에서만 변수가 쓰인다면 변수를 반복문 안에 작성하는 게 좋다

 

요소의 스코프 : 요소를 볼 수 있는 컴퓨터 프로그램 영역. 코틀린의 스코프는 기본적으로 중괄호로 만들어지며 내부 스코프에서 외부 스코프에 있는 요소에만 접근할 수 있다.

 

fun main() {
    val a = 1
    fun fizz() {
        val b = 2
        println("a + b : ${a + b}")
    }

    val buzz = {
        val c = 3
        print("a + c : ${a + c}")
    }
}

 

위 예시에서 fizz, buzz()의 스코프에선 외부 스코프에 있는 변수에 접근할 수 있다. 하지만 외부 스코프에선 내부 스코프에 정의된 변수에 접근할 수 없다.

아래는 변수 스코프를 제한하는 예시다. (main 위의 2줄은 내가 임의로 추가했다)

 

class User(val name: String)
val users = listOf(User("a"), User("b"), User("c"))

fun main() {
    // 나쁜 예
    var user: User
    for (i in users.indices) {
        user = users[i]
        println("User at $i is ${user.name}")
    }
    
    // 좀 더 좋은 예
    for (i in users.indices) {
        val user = users[i]
        println("User at $i is ${user.name}")
    }
    
    // 제일 좋은 예
    for ((i, user) in users.withIndex()) {
        println("User at $i is ${user.name}")
    }
}

 

1번 예에서 변수 user는 for문 내부, 외부에서도 쓸 수 있지만 2, 3번 예에선 user의 스코프를 for문 내부로 제한한다. 최대한 변수는 스코프를 좁게 설정하는 게 좋다.

 

스코프를 좁게 만드는 가장 중요한 이유 : 프로그램 추적, 관리가 쉽기 때문

변수 스코프 범위가 너무 넓으면 다른 개발자에 의해 변수가 잘못 쓰일 수 있다. 다른 개발자가 이걸 써서 코드를 구성하면 또 다른 개발자들이 이 코드를 이해하기 어려울 것이다.

 

변수는 읽기 전용, 읽고 쓰기 전용 여부와 상관없이 변수를 정의할 때 초기화되는 게 좋다. if, when, try-catch, Elvis 표현식을 활용하면 최대한 변수를 정의할 때 초기화할 수 있다

 

fun main() {
    // 나쁜 예
    val user: User
    if (hasValue) {
        user = getValue()
    } else {
        user = User()
    }
    
    // 조금 더 좋은 예
    val user: User = if (hasValue) {
        getValue()
    } else {
        User()
    }
}

 

여러 프로퍼티를 한꺼번에 설정해야 하는 경우엔 구조분해 선언(destructuring declaration)을 활용하는 게 좋다.

 

fun main() {
    // 나쁜 예
    fun updateWeather(degrees: Int) {
        val description: String
        val color: Int
        if (degrees < 5) {
            description = "cold"
            color = BLUE
        } else if (degrees < 23) {
            description = "mild"
            color = YELLOW
        } else {
            description = "hot"
            color = RED
        }
        // ...
    }
    
    // 좀 더 좋은 예
    fun updateWeather2(degrees: Int) {
        val (description, color) = when {
            degrees < 5 -> "cold" to BLUE
            degrees < 23 -> "mild" to YELLOW
            else -> "hot" to RED
        }
    }
}

 

결론 : 변수 스코프가 넓으면 위험하다

 

캡쳐링

 

시퀀스 빌더를 써서 에라토스테네스의 체(소수 구하는 알고리즘)를 구현하란 문제가 있다. 알고리즘 자체는 개념적으로 단순하다.

 

  1. 2부터 시작하는 숫자 리스트(2..100 등)를 만든다
  2. 1번째 요소를 선택한다. 2기 때문에 소수다
  3. 남아있는 숫자 중 2번에서 선택한 소수로 나눌 수 있는 모든 숫자를 제거한다

 

이걸 구현하면 아래와 같다.

 

fun main() {
    var numbers = (2..100).toList()
    val primes = mutableListOf<Int>()
    while(numbers.isNotEmpty()) {
        val prime = numbers.first()
        primes.add(prime)
        numbers = numbers.filter {
            it % prime != 0
        }
    }
    print(primes)
    // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
}

 

아래는 시퀀스를 활용하는 방식으로 확장한 것이다.

 

fun main() {
    val primes: Sequence<Int> = sequence {
        var 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]
}

 

이걸 최적화하려는 사람들은 항상 존재한다. prime을 var로 선언하고 반복문 안에서 계속 만드는 게 아니라 반복문 진입 전에 1번만 생성하는 형태다.

 

fun main() {
    val primes: Sequence<Int> = sequence {
        var 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]
}

 

그러나 실행 결과는 이상하다. 이런 결과가 나온 이유는 prime 변수를 캡쳐했기 때문이다.

이런 문제가 발생할 수 있어 항상 잠재적인 캡쳐 문제를 주의해야 한다. 가변성을 피하고 스코프 범위를 좁게 만들면 이런 문제를 간단하게 피할 수 있다.

반응형
Comments