이펙티브 코틀린 1장 아이템2 - 변수의 스코프를 최소화하라
아이템2의 주제: 변수의 스코프를 최소화 하는것이 좋다
스코프란?
- 어떤 요소의 스코프라고 한다면 그 요소를 볼 수 있는 컴퓨터 프로그램 영역이다.
- 변수의 스코프라고 한다면 그 변수를 인식하고 사용할 수 있는 범위를 의미한다.
- 코틀린에서는 기본적으로 {} 중괄호로 스코프를 만든다.
상태를 정의할때는 변수와 프로퍼티의 스코프를 최소화 하는 것이 좋다.
- 프로퍼티 보단 지역변수를 사용하는것이 좋다.
- 최대한 좁은 스코프를 갖도록 변수를 사용한다.
스코프 범위 좁히기의 예시
// 가장 안좋은 방법
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")
}
변수는 정의할때 초기화하는것이 좋다
- if, when, try-catch, Elvis 표현식을 활용하면 변수를 정의할때 초기화하는것이 편해진다.
// if
val user: User = if(hasValue) {
getValue()
} else {
User()
}
// when
val dayType = when (val dayOfWeek = "Monday") {
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "Weekday"
"Saturday", "Sunday" -> "Weekend"
else -> "Invalid day"
}
println(dayType) // 출력: Weekday
// try-catch
val result: Int = try {
// 예외가 발생할 수 있는 코드
val numerator = 10
val denominator = 2
numerator / denominator
} catch (e: ArithmeticException) {
// 예외가 발생한 경우 처리할 코드
println("ArithmeticException caught: ${e.message}")
0 // 예외가 발생했을 때 반환할 값
}
println(result) // 출력: 5
// Elvis
val input: String? = null
val result: String = input ?: "Default Value"
println(result) // 출력: Default Value
- 여러 프로퍼티를 한번에 설정할때는 구조분해선언을 활용하는것이 좋다
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
}
}
캡처링
캡처링이 뭘까요?
캡처링(=람다 캡처링): 람다 블럭 외부에서 정의된 변수를 참조하는것
람다식: 다른 함수에 매개변수로 넘길수있는 함수, 중괄호로 묶인다
fun printMessageWithPrefix(messages: Collection<String>, prefix: String) {
var clientErrors = 0
var serverErrors = 0
messages.forEach {
if(it.startsWith("4")) {
clientErrors++ //lamda capture
}else if(it.startsWith("5") {
serverErrors++ //lamda capture
}
}
println("$clientErrors client errors, $serverErrors server errors")
}
하지만 이런 캡처링때문에 문제가 생길 수 있다
아래는 소수를 구하는 코틀린 코드이다
fun runWithSequenceBuilder() {
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 }// prime의 배수를 필터링
}
}
println(primes.take(25).toList()) // sequence에 쌓인것중 25개를 List로 변환한다.
}
//[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]
이렇게 코드를 작성하면 정상 작동한다
하지만 prime 변수를 while 밖으로 빼면 어떻게 될까
fun runWithSequenceBuilderButWrongScope() {
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(25).toList())
}
//[2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]
prime 의 선언을 var로 하고 while문 밖에 선언했다
그러면 var 이니깐 while돌면서 prime값이 바뀌면서~ 잘 돌아가겠징? 하지만 아님
시퀀스는 게으른 연산(lazy evaluation)을 사용한다. 위 코드에서 primes 는 시퀀스로 만들어져 있다
filter, drop과 같은 중간 연산에서는 바로 연산을 수행하지 않고 종단 연산인 first를 만날때까지 연산을 누적해서 기다린다.
그래서
1번 순환
prime = 2
filter 대기
drop 대기
2번순환
filter 대기
- 참고한 사이트
https://velog.io/@kartmon61/Effective-Kotlin-%EC%BA%A1%EC%B3%90%EB%A7%81capturing%EC%9D%B4%EB%9E%80
https://lovia98.github.io/blog/kotlin-lamda.html
https://bottom-to-top.tistory.com/64
https://iosroid.tistory.com/79