아이템 2 - 변수의 스코프를 최소화하라

아이템 2 - 변수의 스코프를 최소화하라를 정리한 내용입니다.

  • 상태 정의시 변수, 프로퍼티의 스코프를 최소화하는 것이 좋다.

  • 프로퍼티 보다는 지역 변수를 사용하도록 한다.

  • 최대한 좁은 스코프를 갖게 변수를 사용하도록 한다.

  • 코틀린의 스코프는 기본적으로 중괄호로 만들어진다.

val a = 1
fun fizz() {
    val b = 2
    print(a + b)
}
val buzz = {
    val c = 3
    print(a + c)
}
// 여기서는 a는 사용 가능하나, b와 c는 사용할 수 없다.
- 외부에서 내부 스코프의 요소에 접근할 수 없다.
- 내부에서는 외부의 스코프 요소에 접근할 수 있다.
// 나쁜 예
var user: User
for (i in users.indices) {
    user = users[i]
    print("User at $i is $user)
}
- 스코프를 내/외부에서 사용할 수 있게 하는건 좋지 않다.
  • 스코프를 좁게 만들면 프로그램을 추적하고 관리하기 쉽다.

  • 프로그램의 변경 포인트가 많으면 프로그램을 이해하기 어려워진다.

  • mutable 프로퍼티의 스코프가 넓어지면 코드를 이해하기 어려워진다.

  • 변수는 if, when, try-catch, Elvis를 표현식들을 활용해 가능한 정의할 때 초기화 하는 것이 좋다.

// 나쁜 예
val user: User
if (hasValue) {
    user = getValue()
} else {
    user = User()
}

캡쳐링

  • 에라토스테네스의 체(소수를 구하는 알고리즘)를 구현해본다.

var numbers = (2..100).toList()
val primes = mutableListOf<Int>()
while (numbers.isNotEmpty()) {
    val prime = numbers.first()
    primes.add(prime)
    numbers = number.filter { it % prime != 0 }
}
print(primes) // [2, 3, 5, 7, 11, ..., 71, 73, 79, 83, 89, 97]
1. 2부터 100까지 숫자 리스트를 만든다.
2. 첫 번째 요소를 선택한다. (소수)
3. 남아 있는 수를 2번에서 선택한 소수로 나누어지는 모든 숫자를 제거한다. (반복)
  • 시퀀스를 활용해 소수 구하는 프로그램을 구현해본다.

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]
반복
first() 호출시 numbers 함수 체인
함수 체인에 걸리지는 값
prime

1

없음

없음

2

2

drop(1).filter { it % 2 != 0 }

drop 2

3

3

drop(1).filter { it % 2 != 0 }

drop(1).filter { it % 3 != 0 }

drop 2, filter 4 drop 3

5

4

drop(1).filter { it % 2 != 0 }

drop(1).filter { it % 3 != 0 } drop(1).filter { it % 5 != 0 }

drop 2, filter 4, 6 drop 3, drop 5

7

5

drop(1).filter { it % 2 != 0 }

drop(1).filter { it % 3 != 0 } drop(1).filter { it % 5 != 0 } drop(1).filter { it % 7 != 0 }

drop 2, filter 4, 6, 8, 10 drop 3, filter 9 drop 5 drop 7

11

...

3, 17, 19, 23, 29

  • 시퀀스의 지연연산으로 인해 prime 변수가 최종값으로 캡쳐 되었을때는 전혀 다른 결과가 나온다.

val primes: Sequence<Int> = sequence {
    var numbers = generateSequence(2) { it + 1 }
    
    var prime
    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]
반복
first() 호출시 numbers 함수 체인
함수 체인에 걸리지는 값
prime

1

없음

없음

2

2

drop(1).filter { it % 2 != 0 }

drop 2

3

3

drop(1).filter { it % 3 != 0 }

drop(1).filter { it % 3 != 0 }

drop 2, filter 3 drop 4,

5

4

drop(1).filter { it % 5 != 0 }

drop(1).filter { it % 5 != 0 } drop(1).filter { it % 5 != 0 }

drop 2, filter 5 drop 3, drop 4,

6

5

drop(1).filter { it % 6 != 0 }

drop(1).filter { it % 6 != 0 } drop(1).filter { it % 6 != 0 } drop(1).filter { it % 6 != 0 }

drop 2, filter 6 drop 3, drop 4, drop 5,

7

...

8, 9, 10, 11, 12

- 람다식 안에서의 변수 캡쳐 문제는 주의해야 한다.

정리

변수의 스코프는 좁게 만들어서 사용하는 것이 좋다.

var 보다는 val을 사용하는 것이 좋다.

람다에서 변수를 캡처한다는 사실을 꼭 기억해야 한다.

Last updated