본문 바로가기
개발/Java, Kotlin

[Effective Kotlin] Item 17: Consider naming arguments

by 달사쿠 2021. 8. 25.
반응형

 


Intro

val text = (1..10).joinToString("|")

위와 같은 예시가 있을 때, joinToString에 대해서 모른다면 "|"이 seperator라는 것을 알기 어렵고, 명확하지 않습니다.

(물론 요즘은 IntelliJ와 같은 IDE가 잘알려주지만,,, 그래도!)

 

가장 좋은 방법은 naming argument를 사용하는 것 입니다. 아래처럼요!

val text = (1..10).joinToString(separator = "|")

 

비슷한 방법으로 naming variable이 있습니다.

val separator = "|"
val text = (1..10).joinToString(separator)

위의 경우, naming argument보다는 좀 덜 안정적입니다.

변수 명은 개발자가 의도를 갖고 네이밍할 수는 있지만,

  • 변수명이 모호할 수도 있고
  • 잘못된 위치에 해당 변수를 배치했다면

문제가 발생할 수 있습니다. 어쨌거나 naming argument가 합리적인 방법으로 보여집니다.

 


언제 named arguements를 사용해야 할까?

named arguments의 장점

  • Name은 예상되는 값을 가리킨다.
  • 순서와 무관하기 때문에 안전하다.

argument name은 function을 사용하는 개발자에게나 어떻게 사용하는지를 읽는 개발자 모두에게 중요한 정보입니다.

아래 예로 들자면, 얼마만큼 sleep이 걸려있는지를 알기 어렵습니다. 하지만 named argument를 사용하면 좀 더 쉽게 이해할 수 있습니다.

sleep(100)		//named argument 미사용

sleep(timeMillis = 100) //named argument 사용

 

아래의 3가지에 대해서 named argument는 특히 사용을 고려되어야 합니다.

  • default argument와 사용할 때 (Parameters with default argument)
  • 같은 type의 다른 parmamer들과 함께 사용할 때 (Many parameters with the same type)
  • 마지막 parameter가 아닌 functional type에 대해서

 

Parameters with default argument

파라미터에 deafult argument가 있다면 꼭 name을 사용하세요.

일반적으로, 함수 이름에 인수가 무엇인지 나타나지만 선택사항은 잘 표시되지 않습니다. 또한 이런 옵셔널한 parameter는 필수 parameter보다 더 자주 변경되기 때문에, name을 지정하는 것이 안전하고 깨끗합니다.

 

Many parameters with the same type

매개변수의 type이 모두 다를 때는 잘못된 위치에 argument를 배치해도 안전하지만,동일한 유형이 여러개일 경우에는 그렇지 않습니다.

따라서 아래의 예시처럼 명확하게 사용하는 것이 중요합니다.

fun sendEmail(to: String, message: String) { /* ... */ }

//use case
sendEmail(
    to = "contact@kt.academy",
    message = "Hello, ..."
)

 

Parameters of function type

Kotlin에서는 function type이 매개변수 마지막 위치에 있을경우, 특별하게 사용될 수 있습니다. 

예를 들자면 아래와 같이 { } 블록이 매개인자로 표현될 수 있습니다.

thread(threadFunc: () -> Unit) {
    threadFunc()
}

//use case
thread {
    // threadFunc logic
}

따라서 함수 명이 function type의 매개변수를 표현할 수 있을 때 이 경우가 유용합니다.

(repeat나 thread와 같이 다음 행동이 예상되는 function)

function type와 함께있는 모든 argument는 잘못해석되기 쉽기때문에 꼭 name을 붙여야합니다.

 

먼저 아래와 같은 간단한 DSL예시를 보자면, 어떤 function이 builder인지 on-click listener인지 알 수 없습니다.

val view = linearLayout {
    text("Click below")
    button({ /* 1 */ }, { /* 2 */ })
}

이럴 때 아래와 같이 lisner는 naming을 붙여주고, builder를 arguement 바깥으로 옮겨주는 것이 좀더 명확합니다.

val view = linearLayout {
    text("Click below")
    button(onClick = { /* 1 */ }){ 
        /* 2 */ 
    }
}

 

여러 개의 옵셔널한 function type의 argument를 갖는 경우엔 더더더더더ㅓ욱 혼란을 야기할 수 있습니다.

아래로 예를 들면, ()가 묶여지느냐에 따라 CALLMiddle이 출력될 수 있고 MiddleCALL이 출력될 수 있습니다.

fun call(before: ()->Unit = {}, after: ()->Unit = {}){
    before()
    print("Middle")
    after()
}

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

이런 혼란을 막기위해서는 name을 붙여주는 것이 명확하고 좋습니다.

call(before = { print("CALL") })   //CALLMiddle
call(after = { print("CALL") })    //MiddleCALL

 

이런 특징은 java에서 주석으로 달아 이해를 돕는 것과 다르게 name을 사용해 확연히 명확한 것을 확인할 수 있습니다.

Java에서는 next, error, complete function을 주석을 통해 명확하게 나타냈다.
Kotlin에서는 name을 사용해서 각각 function을 명확하게 표현했다.

 


Summary

  • Named argument는 코드를 읽는 개발자에게 중요한 정보를 제공하고, 코드의 안정성을 향상시킬 수 있습니다.
  • 동일한 type을 갖는 parameters나 functional type을 갖는 parameters, optional한 parameter를 갖는 경우 특히나 고려해야합니다.
반응형

댓글