- 상태를 정의할 때는 변수와 프로퍼티의 소코프를 최소화하는 것이 좋음
- 프로퍼티보다는 지역 변수 사용
- 최대한 좁은 스코프를 갖게 되는 변수 사용
- 요소의 스코프라는 것은 요소를 볼 수 있는 컴퓨터 프로그램 영역
- 코틀린의 스코프는 기본적으로 중괄호로 만들어지며, 내부 스코프에서 외부 스코프에 있는 요소에만 접근할 수 있음
//안좋은 예시
val a = 1
fun fizz() {
val b = 2
println(a + b)
}
val buzz = {
val c = 3
println(a + c)
}
//변수 스코프를 제한하는 예
val users = listOf<User>()
var user: User
// 외부/내부 모두 사용할 수 있는 변수 사용
for (i in users.indices) {
user = users[i]
println("User at $i is $user")
}
// user 스코프를 내부로 제한
for (i in users.indices) {
val user = users[i]
println("User at $i is $user")
}
for ((i, user) in users.withIndex()){
println("User at $i is $user")
}
- 스코프를 좁게 만드는 것이 좋은 가장 큰 이유는 프로그램을 추적하고 관리하기 쉽기 때문임
- 스코프 범위가 너무 넓으면 다른 개발자에 의해 변수가 잘못 사용될 수 있음
- 추가로 변수 정의시에도 초기화하는 것이 좋은데 코틀린은 if, when, try-catch, Elvis 표현식 등을 활용하면 최대한 변수를 정의할때 초기할 수 있음
// bad
val user: User
//HasValue가 true면 속성으로 현재 Nullable<T> 개체의 값에 Value 액세스할 수 있음
//그렇지 않으면 해당값에 액세스하려고 시도하면 InvaildOperationException throw 발생
if (hasValue) {
user = getValue()
} else {
user = User()
}
// good
val user: User = if (hasValue) {
getValue()
} else {
User()
}
// 구조분해 선언
// bad
fun updateWeather(degrees: Int) {
val description: String
val color: String
if (degrees < 5) {
description = "cold"
color = "BLUE"
} else if (degrees < 23) {
description = "mild"
color = "YELLOW"
} else {
description = "hot"
color = "RED"
}
}
// good
fun updateWeather(degrees: Int) {
val (description, color) = when {
degrees < 5 -> "color" to "BLUE"
degrees < 23 -> "mild" to "YELLOW"
else -> "hot" to "RED"
}
}
넓은 스코프 범위의 문제 : 캡처링
- 에라토스테네스의 체 (소수를 구현하느 알고리즘)를 통해 캡처링 이슈 파악
- 2부터 시작하는 숫자 리스트 만듬
- 첫번째 요소를 선택
- 남아 있는 숫자 중에서 2번에서 선택한 소수로 나눌 수 있는 모든 숫자를 제거
- 에라토스테네스의 체 구현
구현 예
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 }
}
println(primes)
시퀀스를 활용한 구현 예
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
}
}
}
println(primes.take(10).toList()) // [2, 3, 5, 7. 11, 13, 17, 19, 23, 29]
잘못 활용한 예
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
}
}
}
println(primes.take(10).toList()) // [2, 3, 5, 6, 7, 8, 9, 10, 11, 12]
- 위 결과가 잘못 나온 이유
- 필터링은 시퀀스를 사용하기 때문에 나중에 실행되는데, 모든 스텝에서 점점 필터가 체이닝 되는데 위 코드에서는 항상 변경 가능한 prime 을 참조하게 됨
- 따라서 항상 가장 마지막의 prime 값으로만 필터링 된 것
- 이러한 잠재적인 캡처 문제등이 발생할 수 있으므로 가변성을 피하고 스코프 범위를 좁게 만들어야 함
정리
- 변수의 스코프는 좁게 만들어서 활용하는 것이 좋고 var보다는 val을 사용하는 것이 좋음
- 람다에서 변수를 캡처한다는 것을 꼭 기억해야함
'도서 > 이펙티브 코틀린' 카테고리의 다른 글
Item4 - inferred 타입으로 리턴하지 말라 (0) | 2022.09.24 |
---|---|
item3 - 최대한 플랫폼 타입을 사용하지 마라 (0) | 2022.09.24 |