일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- android retrofit login
- 큐 자바 코드
- android ar 개발
- 클래스
- 서비스 쓰레드 차이
- Rxjava Observable
- 2022 플러터 안드로이드 스튜디오
- rxjava disposable
- 안드로이드 레트로핏 사용법
- 안드로이드 라이선스 종류
- 멤버변수
- 안드로이드 유닛 테스트 예시
- rxjava cold observable
- 안드로이드 os 구조
- ar vr 차이
- 안드로이드 라이선스
- 안드로이드 유닛 테스트
- 안드로이드 유닛테스트란
- 2022 플러터 설치
- 서비스 vs 쓰레드
- 스택 자바 코드
- jvm이란
- ANR이란
- 객체
- 플러터 설치 2022
- 자바 다형성
- rxjava hot observable
- 스택 큐 차이
- 안드로이드 레트로핏 crud
- jvm 작동 원리
- Today
- Total
나만을 위한 블로그
[이펙티브 코틀린] 아이템 15. 리시버를 명시적으로 참조하라 본문
뭔가를 더 자세하게 설명하기 위해 명시적으로 긴 코드를 쓸 때가 있다. 대표적으로 함수, 프로퍼티를 지역 또는 톱레벨 변수가 아닌 다른 리시버로부터 가져온다는 걸 나타낼 때가 있다. 클래스의 메서드라는 걸 나타내기 위한 this가 그 예시다.
class User: Person() {
private var beersDrunk: Int = 0
fun drinkBeers(num: Int) {
// ...
this.beersDrunk += num
// ...
}
}
비슷하게 확장 리시버(확장 메서드에서의 this)를 명시적으로 참조하게 할 수도 있다. 비교를 위해 일단 리시버를 명시적으로 표시하지 않은 퀵소트 구현을 확인한다.
fun <T: Comparable<T>> List<T>.quickSort(): List<T> {
if (size < 2) return this
val pivot = first()
val (smaller, bigger) = drop(1)
.partition { it < pivot }
return smaller.quickSort() + pivot + bigger.quickSort()
}
명시적으로 표현하면 아래와 같다.
fun <T: Comparable<T>> List<T>.quickSort(): List<T> {
if (this.size < 2) return this
val pivot = this.first()
val (smaller, bigger) = this.drop(1)
.partition { it < pivot }
return smaller.quickSort() + pivot + bigger.quickSort()
}
두 함수 사용에 차이는 없다.
여러 개의 리시버
스코프 내부에 둘 이상의 리시버가 있는 경우 리시버를 명시적으로 나타내면 좋다. apply, with, run 함수를 사용할 때가 대표적인 예시다. 아래 코드를 확인한다.
class Node(val name: String) {
fun makeChild(childName: String) =
create("$name.$childName")
.apply {
print("Created $name")
}
fun create(name: String): Node? = Node(name)
}
fun main() {
val node = Node("Parent")
node.makeChild("child")
}
일반적으로 위 코드의 결과가 'Created parent.child'가 출력된다고 예상하지만 실제로는 'Created Parent'가 출력된다. 앞에 명시적으로 리시버를 붙이면 아래와 같다.
class Node(val name: String) {
fun makeChild(childName: String) =
create("$name.$childName")
.apply {
print("Created ${this.name}")
}
fun create(name: String): Node? = Node(name)
}
문제는 apply 함수 내부에서 this 타입이 Node?라서 이를 직접 사용할 수 없어 컴파일 에러가 난다. 이를 사용하려면 언팩하고 호출해야 한다. 이렇게 하면 일반적으로 생각하는 답이 나온다.
class Node(val name: String) {
fun makeChild(childName: String) =
create("$name.$childName")
.apply {
print("Created ${this?.name}")
}
fun create(name: String): Node? = Node(name)
}
fun main() {
val node = Node("Parent")
node.makeChild("child")
}
이건 apply의 잘못된 사용 예다. also 함수와 파라미터 name을 썼다면 이런 문제 자체가 일어나지 않는다.
also를 쓰면 이전과 마찬가지로 명시적으로 리시버를 지정하게 된다. 일반적으로 also 또는 let을 쓰는 게 nullable 값을 처리할 때 훨씬 좋은 선택지다.
리시버가 명확하지 않다면 명시적으로 리시버를 적어서 이를 명확하게 하라. 레이블 없이 리시버를 쓰면 가장 가까운 리시버를 의미한다. 외부에 있는 리시버를 쓰려면 레이블을 써야 한다. 둘 모두를 사용하는 예를 확인한다.
class Node(val name: String) {
fun makeChild(childName: String) =
create("$name.$childName")
.apply {
print("Created ${this?.name} in ${this@Node.name}")
}
fun create(name: String): Node? = Node(name)
}
fun main() {
val node = Node("Parent")
node.makeChild("child")
}
어떤 리시버를 사용하는지 의미가 더 명확해졌다. 이렇게 명확하게 작성하면 코드를 안전하게 사용할 수 있고 가독성도 향상된다.
DSL 마커
코틀린 DSL을 쓸 때는 여러 리시버를 가진 요소들이 중첩되더라도 리시버를 명시적으로 붙이지 않는다. DSL은 원래 그렇게 쓰도록 설계됐기 때문이다. 그런데 DSL에선 외부 함수를 사용하는 게 위험한 경우가 있다.
DSL 마커는 가장 가까운 리시버만을 사용하게 하거나 명시적으로 외부 리시버를 사용하지 못하게 할 때 활용할 수 있는 중요한 매커니즘이다. DSL 설계에 따라서 사용 여부를 결정하는 것이 좋으므로 설계에 따라서 사용하자.
'책 > Effective Kotlin' 카테고리의 다른 글
[이펙티브 코틀린] 아이템 17. 이름 있는 아규먼트를 사용하라 (0) | 2022.08.07 |
---|---|
[이펙티브 코틀린] 아이템 16. 프로퍼티는 동작이 아닌 상태를 나타내야 한다 (0) | 2022.07.24 |
[이펙티브 코틀린] 아이템 14. 변수 타입이 명확하지 않은 경우 확실하게 지정하라 (0) | 2022.07.18 |
[이펙티브 코틀린] 아이템 13. Unit?을 리턴하지 마라 (0) | 2022.07.10 |
[이펙티브 코틀린] 아이템 12. 연산자 오버로드를 할 때는 의미에 맞게 써라 (0) | 2022.07.10 |