코틀린의 스코프 함수(Scope Function) 정리
스코프 함수는 코틀린 표준 라이브러리에 포함된 함수로, 특정 객체의 컨텍스트 내에서 코드 블록을 실행할 수 있게 해준다. 이를 통해 코드를 더 간결하고 가독성 높게 만들 수 있으며, 특히 null 처리나 객체 초기화 시에 유용하게 사용된다.
1. 스코프 함수란?
스코프 함수는 람다를 사용하여 일시적인 영역(scope)을 만들고, 그 안에서 객체의 이름 없이 프로퍼티나 함수에 접근할 수 있게 해주는 함수다.
예를 들어, null이 아닐 때만 객체의 프로퍼티를 출력하는 코드는 다음과 같이 개선할 수 있다.
기존 코드
fun printPerson(person: Person?) {
if (person != null) {
println(person.name)
println(person.age)
}
}
스코프 함수(let) 적용 후
fun printPersonScope(person: Person?) {
// person이 null이 아니면 let 블록을 실행
person?.let {
println(it.name)
println(it.age)
}
}
let은 확장 함수로 구현되어 있으며, 람다를 인자로 받아 그 결과를 반환한다. 이처럼 스코프 함수는 람다를 활용해 코드를 간결하게 만들고, 메서드 체이닝에 활용하는 함수를 의미한다.
2. 스코프 함수의 분류
스코프 함수는 크게 5가지(let, run, apply, also, with)가 있으며, 두 가지 기준에 따라 분류할 수 있다.
반환 값
- 람다의 결과 반환:
let,run,with - 객체 자신(context object) 반환:
apply,also
- 람다의 결과 반환:
컨텍스트 객체 참조 방식
it으로 참조 (argument):let,alsothis로 참조 (receiver):run,apply,with
| 함수 | 컨텍스트 객체 | 반환 값 | 특징 |
|---|---|---|---|
let |
it |
람다 결과 | non-null 변수에 대한 코드 실행, 변수 scope 제한 |
run |
this |
람다 결과 | 객체 초기화와 결과 계산을 동시에 할 때 |
apply |
this |
객체 자신 | 객체 설정 및 초기화 (Test Fixture 등) |
also |
it |
객체 자신 | 객체에 대한 부가 작업 (로깅 등) |
with |
this |
람다 결과 | 비확장 함수, 객체를 인자로 받아 사용 |
it 과 this 의 차이와 원인
this: 생략이 가능하지만, 다른 이름으로 변경할 수 없다.it: 생략이 불가능하지만, 람다 파라미터처럼 다른 이름으로 변경할 수 있다. (person.let { p -> p.age })
이러한 차이는 각 스코프 함수가 받는 람다의 시그니처가 다르기 때문에 발생한다.
let은 일반 함수(T) -> R를 받기 때문에, 파라미터를it으로 받는다.run은 확장 함수T.() -> R를 받기 때문에, T 자기 자신을this로 참조한다.
3. 언제 어떤 스코프 함수를 사용해야 할까?
3-1) let
- 하나 이상의 함수를 call chain 결과로 호출할 때
val strings = listOf("APPLE", "CAR") strings.map { it.length } .filter { it > 3 } .let(::println) // 최종 결과를 println의 인자로 넘김 non-null값에 대해서만 코드를 실행시킬 때val length = str?.let { println(it.uppercase()) it.length }- 일회성으로 제한된 영역에 지역 변수를 만들 때
val numbers = listOf("one", "two", "three", "four") val modifiedFirstItem = numbers.first() .let { firstItem -> if (firstItem.length >= 5) firstItem else "!$firstItem" }.uppercase() println(modifiedFirstItem)
3-2) run
객체 초기화와 반환 값의 계산을 동시에 해야 할 때
// Person 객체를 생성 후 DB에 저장하고, 저장된 인스턴스를 반환받음 val person1 = Person("김태일", 100).run(personRepository::save) // 객체에 추가적인 처리를 한 후 로직을 실행할 때 val person2 = Person("김태일", 100).run { hobby = "독서" personRepository.save(this) // 저장된 person2 객체가 반환됨 }
3-3) apply
- 객체 설정을 할 때 (특히 Test Fixture 생성 시 유용)
fun createPerson(name: String, age: Int, hobby: String): Person { return Person(name = name, age = age) .apply { this.hobby = hobby // this는 Person 객체, 반환값도 Person 객체 } }
3-4) also
- 객체에 대한 부가적인 작업(side effect)이 필요할 때 (로깅 등)
mutableListOf("one", "two", "three") .also { println("four 추가 이전 지금 값: $it") } // it은 리스트 자신 .add("four")
3-5) with
- 특정 객체를 다른 객체로 변환해야 할 때 (non-extension function)
with는 확장 함수가 아니기 때문에, 인자로 객체를 직접 받는다.return with(person) { PersonDto( name = name, // this.name age = age // this.age ) }
4. 스코프 함수와 가독성
스코프 함수는 코드를 간결하게 만들지만, 남용하면 오히려 가독성을 해칠 수 있다.
이펙티브 코틀린 예시
// 1. if-else 사용
if (person != null && person.isAdult) {
view.showPerson(person)
} else {
view.showError()
}
// 2. 스코프 함수 사용
person?.takeIf { it.isAdult }
?.let(view::showPerson)
?: view.showError()
두 번째 코드는 간결하지만, 코틀린에 익숙하지 않은 사람에게는 if-else 구문이 더 명확하고 디버깅하기 쉬울 수 있다. 사용 빈도가 낮은 관용구를 조합하면 복잡성이 증가할 수 있다.
따라서 스코프 함수는 팀의 컨벤션에 맞춰 적절하게 사용하는 것이 중요하겠다..~
우리팀에서는 이미 많이 써놨던데..
최종 요약
- 코틀린의 스코프 함수는 일시적인 영역을 만들어 코드를 더 간결하게 하거나, 메서드 체이닝에 활용된다.
- 스코프 함수의 종류에는
let/run/also/apply/with가 있으며, 반환 값과 컨텍스트 객체 참조 방식에 따라 구분된다. - 스코프 함수를 사용한 코드는 사람에 따라 가독성을 다르게 느낄 수 있으므로, 함께 프로덕트를 만들어 가는 팀끼리 컨벤션을 잘 정하는 것이 중요하겠다..!.
'kotlin' 카테고리의 다른 글
| [Kotlin] lateinit, by lazy (0) | 2025.11.03 |
|---|---|
| [Kotlin] 코틀린의 이것저것 문법 (0) | 2025.09.07 |
| [Kotlin] 컬렉션을 함수형으로 다루기 (1) | 2025.08.28 |
| [Kotlin] 람다 (Lambda) (0) | 2025.08.20 |
| [Kotlin] 다양한 함수 (1) | 2025.08.18 |