
코틀린 scope function은 객체를 잠깐 다른 이름이나 문맥으로 다루게 해 주는 함수입니다. 문제는 let, run, apply, also, with가 비슷해 보여서 처음에는 아무거나 써도 되는 문법처럼 느껴진다는 점입니다.
기준은 의외로 단순합니다. 수신 객체를 this로 볼지 it으로 볼지, 그리고 마지막에 무엇을 반환해야 하는지를 먼저 보면 대부분의 선택이 정리됩니다. 이 글은 Kotlin 공식 문서의 scope function 기준을 바탕으로 실무에서 읽기 쉬운 선택법을 설명합니다.
코틀린 scope function을 고르는 첫 기준
scope function은 새 기능을 만드는 문법이라기보다 코드 블록 안에서 객체를 다루는 방식을 바꾸는 도구입니다. 그래서 함수 이름보다 먼저 두 가지 질문을 던지는 편이 좋습니다.
- 블록 안에서 객체를 this로 다룰 것인가, it으로 다룰 것인가
- 블록이 끝난 뒤 객체 자신을 받을 것인가, 블록의 결과값을 받을 것인가
let은 null 처리와 변환에 잘 맞는다
let은 객체를 it으로 받고, 블록의 마지막 값을 반환합니다. 그래서 nullable 값을 안전하게 열어 보거나, 어떤 값을 다른 값으로 바꾸는 흐름에 잘 맞습니다.
val length = name?.let { value ->
value.trim().length
} ?: 0위 코드에서 관심사는 name 자체가 아니라 name을 정리한 뒤 길이로 바꾸는 결과입니다. 이런 경우 let은 흐름이 자연스럽습니다.
apply는 객체 설정 후 그대로 돌려받을 때 쓴다
apply는 객체를 this로 다루고, 마지막에 객체 자신을 반환합니다. 그래서 객체를 만들고 여러 속성을 설정한 뒤 그대로 넘길 때 적합합니다.
val button = Button(context).apply {
text = "Save"
isEnabled = true
}이 코드의 목적은 설정이 끝난 Button 객체를 얻는 것입니다. 반환값이 객체 자신이어야 하므로 apply가 잘 맞습니다.
also는 중간 확인과 부수 작업에 어울린다
also는 객체를 it으로 받고, 객체 자신을 반환합니다. 객체를 바꾸기보다 로그, 검증, 중간 확인처럼 옆에서 한 번 보는 작업에 잘 맞습니다.
val user = repository.findUser(id)
.also { logger.info("loaded user=${it.id}") }run과 with는 계산 블록으로 읽으면 편하다
run은 this를 쓰고 블록 결과를 반환합니다. with도 비슷하지만 확장 함수가 아니라 객체를 인자로 받습니다. 둘 다 여러 값을 이용해 하나의 결과를 만들 때 읽기 좋습니다.
val label = user.run {
"$name ($email)"
}
val sizeText = with(size) {
"${width}x${height}"
}외우는 표보다 위험한 신호를 기억하자
- scope function을 여러 번 중첩해서
it이 누구인지 헷갈린다 - apply 안에서 계산 결과를 만들려고 한다
- also 안에서 객체 상태를 많이 바꾼다
- 단순한 한 줄을 멋있게 보이게 하려고 scope function을 끼워 넣는다
정리
코틀린 scope function은 많이 쓰는 것보다 정확히 쓰는 것이 중요합니다. null 처리와 변환은 let, 객체 설정은 apply, 중간 확인은 also, 계산 블록은 run 또는 with로 시작하면 대부분의 코드가 더 읽기 쉬워집니다.
내부 글은 코틀린 null safety 정리, 코틀린 lateinit lazy 차이, 코틀린 inline은 왜 쓸까와 함께 보면 좋습니다. 외부 기준은 Kotlin Docs – Scope functions를 확인했습니다.