이펙티브 코틀린 item2: 변수의 스코프를 최소화하라

woga·2023년 2월 4일
0

코틀린 공부

목록 보기
5/54
post-thumbnail

변수의 스코프를 최소화하라

이펙티브 코틀린에서는 변수와 프로퍼티의 스코프를 최소화하라고 한다.

  • 프로퍼티보단 지역 변수가 좋다.

  • 최대한 좁은 스콥을 갖는 변수를 사용한다. (반복문 내부에서만 변수가 사용된다면 반복문 내부에서 변수를 선언해서 쓸 것 등)

왜냐하면 스콥을 최소화해야지 프로그램을 추적하고 관리하기 쉽기 때문이다.

코드를 분석할 때는 어떤 시점에 어떤 사이드가 있는지를 알아야하는데, 이때 파악해야하는 파트들이 많아져서 프로그램에 변경될 수 있는 부분이 많아지면 이해하기가 어렵다.
앞서 가변성을 제한하자라고 말한거처럼 immutable 프로퍼티를 선호하는 이유와 비슷하다.

예제로 한 번 살펴보자

// bad example
var user: User
for (i in users.indices) {
	user = users[i]
    println("User at $i is $user")
}

// better example
for (i in users.indices) {
	val user = users[i]
    println("User at $i is $user")
}

// best example
for ((i, user) in users.withIndex()) {
	println("User at $i is $user")
}

mutable 프로퍼티는 좁은 스콥에 걸쳐 있을수록 변경 추적이 쉽다. 변수의 스콥이 너무 넓으면 다른 개발자에 의해 변수가 잘못 사용될 수도 있다. 그러다가 다른 개발자가 이 변수를 잘못사용한다면? 결국은 또 다른 개발자가 유지보수하려다가 이해하기 어려운 상황에 직면할 수도 있다.

그리고 변수는 읽기 전용 / 읽기 쓰기 전용과 상관없이 변수 정의할 때 초기화를 해주자

캡쳐링

스콥이 넓으면 좋지 않다고 하는데 어떻게 좋지 않은지 더욱 파보자면 바로 캡쳐링이다.

만약 소수를 구하는 알고리즘을 구현하게 된다면 어떻게 구현할 것인가?
간단하게 구현한다면 아래와 같다.

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]

시퀀스를 사용해 이 예제를 더 확장시켜보자

시퀀스에 대해 모르겠다면 해당 링크를 통해 이해하고 보자 -> https://iosroid.tistory.com/79

val primes: Sequence<Int> = sequence {
	var numbers = generateSequence(2) { it + 1 }
    
    while (true) {
    	val prime = numbers.first()
        yield(prime) // sequence에 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<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 변수를 캡쳐했기 때문이다. (+ 코틀린 람다 관련 링크를 첨부하고 간다 https://lovia98.github.io/blog/kotlin-lamda.html)

반복문 내부에서 filter를 활용해서 prime으로 나눌 수 있는 숫자를 필터링하는데 시퀀스를 활용하므로 필터링이 지연된다!
최종적으로 prime 값으로만 필터링되고 처음에 2로 설정했을 때 4를 제외한 거 빼고는 drop만 동작해서 연속된 숫자만 나오게 된다.

그래서 이런 잠재적인 캡쳐 문제를 주의하려면

가변성을 피하고 스코프 범위를 좁게 만든다

라는 결론이 된다.

정리

이처럼 여러 가지 이유로 변수의 스콥은 좁게 만들어서 활용하는 것이 좋고 var 보다는 val을 사용하는 것이 좋다.

그리고 람다에서는 변수를 캡쳐한다는 것을 꼭 기억하자!

profile
와니와니와니와니 당근당근

0개의 댓글