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

아이템17) 이름 있는 아규먼트를 사용하라

막이86 2024. 4. 17. 10:40
728x90
  • 코드에서 아규먼트의 의미가 명확하지 않은 경우가 있음
    • “|”은 무엇을 의미할까?
      • joinToString에 대해서 알지 못하면 “|”에 대한 의미를 알기 어려움
    val text = (1..10).joinToString("|")
    
  • 파라미터가 명확하지 않은 경우에 직접 지정해서 명확하게 만들어 줄수 있음
  • val text = (1..10).joinToString(separator = "|")
  • 변수를 사용해 의미를 명확하게 할 수 있음
    • 실제로 코드에서 제대로 사용되고 있는지 알수 없는 단점
    val separator = "|"
    val text = (1..10).joinToString(separator)
    
  • 이름 있는 아규먼트를 함께 활용 하는 것이 좋음
    • 변수를 잘못 만들어 사용 하는 경우 방지
    • 잘못된 위치에 배치 하는 경우 방지
    val separator = "|"
    val text = (1..10).joinToString(separator = separator)
    

이름 있는 아규먼트는 언제 사용해야 할까?

  • 이름 있는 아규먼트를 사용하면 코드가 길어지지만, 다음과 같은 장점이 있음
    • 이름을 기반으로 값이 무엇을 나타내는지 알수 있음
    • 파라미터 입력 순서와 상관 없으므로 안전 함
  • 아규먼트 이름은 함수를 사용하는 개발자뿐만 아니라 코드를 읽는 다른 사람들에게도 굉장히 중요한 정보
// 100ms 인지 100s인지 명확하지 않음
sleep(100)

// 이름 있는 아규먼트를 활용하여 명확하게 사용 가능
sleep(timeMillis = 100)

// 함수를 만들어 시간 단위를 표현 가능
sleep(Millis(100))

// 확장 프로퍼티로 DSL과 유사한 문법을 만들어 활용 가능
sleep(100.ms)
  • 이름 있는 아규먼트를 사용하지 않으면 순서를 잘못 입력하는 문제가 있을 수 있음
  • 이름 있는 아규먼트를 추천 하는 경우
    • 디폴트 아규먼트의 경우
    • 같은 타입의 파라미터가 많은 경우
    • 함수 타입의 파라미터가 있는 경우(마지막 경우 제외)

디폴트 아규먼트의 경우

  • 프롵퍼티가 디폴트 아규먼트를 가질 경우, 항상 이름을 붙여서 사용하는 것이 좋음
    • 함수 이름은 필수 파라미터들과 관련되어 있기 때문에 디폴트 값을 갖는 옵션 파라미터의 설명이 명확하지 않음

같은 타입의 파라미터가 많은 경우

  • 파라미터가 모두 다른 타입이라면, 위치를 잘못 입력하면 오류가 발생할 것
    • 쉽게 문제를 찾을 수 있음
  • 같은 타입이면 잘못 입력했을 때 문제를 찾아내기 어려울 수 있음
fun sendEmail(to: String, message: String) {}

// 이름 있는 아규먼트를 사용 하는 것을 추천
sendEmail(
	to = "contact@kt.academy",
	message = "Hello, ..."
)

함수 타입 파라미터

  • 함수 타입 파라미터는 마지막 위치에 배치하는 것이 좋음
  • 함수 이름이 함수 타입 아규먼트를 설명해 주기도 함
  • thread 이후 블록이 스레드 본문이라는 것을 쉽게 알 수 있음
  • 모든 함수 타입 아규먼트는 이름 있는 아규먼트를 사용하는 것이 좋음
    • 이해하기 쉬움
    // 어떤 부분이 빌더 부분이고, 어떤 부분이 클릭 리스너일까요?
    val view = linearLayout {
    	text("Click below")
    	button({ /* 1 */ }, { /* 2 */ })
    }
    
    // 이름을 부여하고, 위치를 수정하면 훨씬 명확해짐
    val view = linearLayout {
    	text("click below")
    	button(onClick = { /* 1*/ }) {
    		/* 2 */
    	}
    }
    
  • 여러 함수 타입의 옵션 파라미터가 있는 경우에는 더 어려움
fun call(before: () -> Unit = {}, after: () -> Unit = {}) {
	before()
	print("Middle")
	after()
}

fun main() {
    call({ print("CALL") }) // CALLMiddle
    call { print("CALL") }  // MiddleCALL
}

// 이름을 붙여 사용하면 쉽게 이해 가능
fun main() {
	call(before = { print("CALL") }) // CALLMiddle
	call(after = { print("CALL") })  // MiddleCALL
}
  • 리액티브 라이브러리에서 굉장히 자주 볼 수 있는 형태
    • Observable을 구독할 때 함수 설정
      • 각각의 아이템을 받을 때(onNext)
      • 오류가 발생했을 때(onError)
      • 전체가 완료되었을 때(onComplete)
  • 자바에서 람다 표현식을 사용해서 코드 작성
obserable.getUsers()
	.subscribe((List<User> users) -> {
		// ...
	}, (Throwable throwable) -> {
		// ...
	}, () -> {
		// ...
	});
  • 코틀린에서 이름있는 아규먼트를 활용해서 의미를 더 명확하게 할 수 있음
obserable.getUsers()
	.subscribeBy(
		onNext = { users: List<User> -> 
			// ...
		},
		onError = { throwable: Throwable -> 
			// ...
		},
		onCompleted = {
			// ...
		}
	)

정리

  • 디폴트 값들을 생략할 때만 유용한 것은 아님
  • 개발자가 코드를 읽을 때도 편리하게 활용
  • 코드의 안정성 향상
  • 함수에 같은 타입의 파라미터가 있는 경우에 활용하면 좋음
728x90