1. 프로퍼티보다는 지역변수를 사용하는 것이 좋다.

2. 최대한 좁은 스코프를 갖게 변수를 사용해야 한다. 예를들어 반복문 내부에서만 변수가 사용된다면, 변수를 반복문 내부에 작성하는 것이 좋다.


0️⃣ 변수의 스코프는 항상 최소화

변수의 스코프 지정

 val a = 1
 fun hello() {
     val b = 2
     print(a + b)
 }
 val world = {
     val c = 3
     print(a + c)
 }
  • hello와 world 함수는 스코프에서는 외부 스코프에 있는 변수에 접근할 수 있다.
  • 외부 스코프에서는 내부 스코프에 정의된 변수에 접근할 수 없다.
 // 나쁜 예시
 var user: User
 for(i in users.indices) {
     user = users[i]
     print("User at $i is $user")
 }
 
 // 조금 더 좋은 예시
 for(i in users.indices) {
     val user = users[i]
     print("User at $i is $user")
 }

 // 제일 좋은 예시
 for((i, user) in users.withIndex()) {
     print("User at $i is $user")
 }
  • 위 코드의 첫 번째 예시에서 변수 user는 for문 스코프 내부뿐만 아니라 외부에서도 사용할 수 있다.
  • 반면에 두 번째와 세 번째 예시는 변수 user의 스코프를 for문 내부로 제한한다.
 // 나쁜 예시
 val user: User
 if(hasValue) {
     user = getValue()
 } else {
     user = User()
 }
 
 // 조금 더 좋은 예시
 val user: User = if(hasVaule) {
     getValue()
 } else {
     User()
 }
  • 위와 같이 변수는 읽기 전용 또는 읽고 쓰기 전용 여부와 상관없이, 변수를 정의할 때 초기화 되는 것이 좋다.
  • 변수를 초기화 할 때 if, try-catch, when, Elvis 표현식 등을 함께 활용한다면, 최대한 변수를 정의할 때 초기화 할 수 있다.

1️⃣ 캡처링

소수를 구하는 알고리즘인 에라토스테네스의 체를 Collection과 Sequence를 활용하여 예시로 구현 해 볼 수 있겠다.

Iterator 방식을 통해 에라토스테네스의 체 구현

 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 ... 83, 89, 97]

Sequence를 이용해 에라토스테네스의 체 구현

정상적으로 작동하는 코드

 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]

여기서 최적화를 고민해 볼 때 다음과 같은 코드로 작성할 수 있다.(작동하지 않는 코드)

 val primes: Sequence<Int> = sequence {
     val numbers = generateSequence(2) { it + 1 } 
     val prime: Int // 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]

출력 결과를 보면 실행 결과가 이상하게 나오는 것을 볼 수 있다. 왜?

prime이라는 변수의 reference에 해당하는 filter 연산

  • sequence 내부에서 filter 함수는 Iterable 연산과는 다르게 요소 하나하나씩 순서대로 연산을 진행한다.
    기존 Iterable에서는 하나의 list가 통째로 filter 함수에 들어가서 연산을 진행하고, 그 결과를 담은 새로운 list를 생성하여 다음 연산에 넘겨주는 형태의 연산 방법을 가지고 있었다.

  • 하지만 sequence에서는 모든 요소를 차례차례 연산하다가 조건에 맞지 않는 값이 들어오면, 해당 변수의 reference에 해당하는 연산 자체를 종료해버린다. 정상적으로 작동하는 코드에서는 반복문을 돌 때 마다 새로운 reference를 생성하여 해당 prime 값에 해당하는 filter 연산을 진행 해 주었고, 정상적인 연산을 수행했다.

  • 반대로 제대로 작동하지 않는 코드에서는 반복문 바깥에 있는 prime 변수를 캡쳐하고, 해당 reference의 값이 4일 때 filter 연산이 종료되어 버렸으므로 그 뒤 부터는 filter 연산이 작동하지 않고 drop() 함수만 동작하기 때문에 연속된 숫자들이 출력되게 되는 것이다.