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

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

// 나쁜 예
// user가 for loop 외부에 존재
var user: User
for (i in users.indices) {
  user = users[i]
  print(user)
}

// 좋은 예
// user가 for loop 내부에 존재
for (i in users.indices) {
  val user = users[i]
  print(user)
}

// 더 좋은 예
// user가 for loop 내부에 존재
for ((i, user) in users.withIndex()) {
  print(user)
}

스코프가 좁을수록 프로그램을 추적하고 관리하기 쉽다.

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

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

// 좋은 예
fun updateWeather(degrees: Int) {
  val (description, color) = when { // 구조분해 선언
    degrees < 5 -> "cold" to Color.BLUE
    degrees < 23 -> "mild" to Color.YELLOW
    else -> "hot" to Color.RED
  }
}

캡처링

시퀀스를 활용해 에라토스테네스의 체(소수를 구하는 알고리즘)를 구하면 다음과 같다.

val primes = 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을 반복문 외부에 선언하면 결과가 이상하게 나온다.

val primes = 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]

시퀀스를 활용하기 때문에 filter의 연산이 지연(lazy evaluation)된다. 따라서 최종적인 prime 값으로만 filter가 평가되기 때문에 prime이 2일때 필터링된 4를 제외하고 drop만 동작한 결과를 반환한다.

불변으로 만들고 스코프 범위를 좁게 하면 이런 문제를 피할 수 있다.

정리

  • 변수의 스코프는 최대한 좁히는 것이 좋다.

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

  • 람다에서는 변수를 캡처한다.

Last updated