코틀린 스터디/이펙티브 코틀린

아이템15) 리시버를 명시적으로 참조하라

막이86 2024. 3. 19. 17:30
728x90

이펙티브 코루틴을 요약한 내용입니다

  • 명시적으로 긴 코드를 사용할 때가 있음
    • 함수와 프로퍼티를 지역 또는 톱레벨 변수가 아닌 다른 리시버로부터 가져온다는 것을 나타낼 때
    class User: Person() {
    	private var beersDrunk: Int = 0
    
    	fun drinkBeers(num: Int) {
    		this.beersDrunk += num
    	}
    }
    
  • 확장 리시버를 명시적으로 참조하게 할 수 있음
  • 리시버를 명시적으로 표시하지 않은 퀵소트 구현
fun <T: Comparable<T>> List<T>.quickSort(); List<T> {
	if (size < 2) {
		return this
	}

	val pivot = first()
	val (smller, bigger) = drop(1).partition { it < pivot }
	return smaller.quickSort() + pivot + bigger.quickSort()
}
  • 명시적으로 표현한 퀵소트
fun <T: Comparable<T>> List<T>.quickSort(); List<T> {
	if (size < 2) {
		return this
	}

	val pivot = this.first()
	val (smller, bigger) = this.drop(1).partition { it < pivot }
	return smaller.quickSort() + pivot + bigger.quickSort()
}
  • 두 함수의 사용에는 차이가 없음
listOf(3, 2, 5, 1, 6).quickSort()
listOf("C", "D", "A", "B").quickSort()

여러 개의 리시버

  • 스코프 내부에 둘 이상의 리시버가 있는 경우 리시버를 명시적으로 나타내면 좋음
    • apply, with, run 함수를 사용할때
    • create에서 받은 name이 아닌 Node의 name 사용
    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 partent 출력
    }
    
  • 명시적으로 리시버를 붙이는 경우
class Node(val name: String) {
	fun makeChild(childName: String) = create("$name.$childNamd").apply {
		print("Created ${this?.name}")
	}

	fun create(name: String): Node? = Node(name)
}

fun main() {
	val node = Node("parent")
	node.makeChild("child")    // Created parent.child 출력
}
  • also 함수의 파라미터 name을 사용했다면 이런 문제는 발생하지 않음
    • also를 사용하면 명시적으로 리시버를 지정
    • also, let을 사용하는 것이 nullable 값을 처리할 때 좋은 선택지
    class Node(val name: String) {
    	fun makeChild(childName: String) = create("$name.$childName").also {
    		print("Created ${it?.name}")
    	}
    
    	fun create(name: String): Node? = Node(name)
    }
    
    fun main() {
    	val node = Node("parent")
    	node.makeChild("child")    // Created parent.child 출력
    }
    
  • 리시버가 명확하지 않다면, 명시적으로 리시버를 적어 명확하게 해줘야 함
    • 레이블 없이 리시버를 사용하면, 가장 가까운 리시버를 의미
    • 외부에 있는 리시버를 사용하려면, 레이블을 사용해야 함
    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")    // Created parent.child in parent 출력
    }
    
  • 명확하게 작성하면 코드를 안전하게 사용할 수 있음
    • 가독성도 향상

정리

  • 짧게 적을 수 있다는 이유만으로 리시버를 제거하지 말기
  • 여러 개의 리시버가 있는 상황 등에서는 리시버를 명시적으로 적어주는 것이 좋음
  • 리시버를 명시적으로 지정하면, 어떤 리시버의 함수인지를 명확하게 알수 있음
    • 가독성 향상
728x90