반응형

코틀린에는 JAVA 의 switch 문이 없습니다. 대신 when 이 있죠.

기존에 자바로 개발하시다가 코틀린을 새롭게 공부하시는 분들은 놓치기 쉬운 특징들이 있는데요.

when 은 표현식으로 사용할 때와 명령문으로 사용할 때 각각 다른 특징을 가집니다.

이번 포스팅에서는 어쩌면 기본적인 문법에 해당할 수 있는 when 이라는 문법에 대해 면밀히 살펴보겠습니다.

 

표현식(expression)으로서의 when

첫 번째로 표현식(expression)으로서의 when 입니다. 코틀린의 가장 큰 특징이라면 함수, if, when 등 자바에서는 명령문으로 사용 되던 문법들이 표현식으로 제공되어 변수에 저장하거나 함수 파라미터, 또는 반환 값에 사용될 수 있다는 것인데요.

 

아래 예시는 어떤 프로모션의 대상인지 검증하는 함수입니다.

조건은 평점 3점을 주거나, 장바구니에 담고 2점을 준 고객입니다.

fun isPromotionTarget(addToCart: Boolean, rates: Int): Boolean {
    if (rates < 2) { return false }
    if (rates > 3) { return false }
    if (rates == 3) { return true }
    return addToCart && rates == 2
}

위 코드는 굉장히 가독성이 떨어지고, 장황하여 오류를 유발할 수 있습니다.

 

when 은 if-else 문이나 표현식을 간단한 형태로 바꿀 수 있습니다. 그럼 when 을 사용하여 리팩토링 해보겠습니다.

fun isPromotionTarget(addToCart: Boolean, rates: Int) = when {
    rates < 2 -> false
    rates > 3 -> false
    rates == 3 -> true
    else -> addToCart && rates == 2
}

이전 if-else 를 사용했던 버전에서는 리턴 타입을 지정해주고, 블록{}을 이용하여 메소드 바디를 만들었습니다. 그리고 when 을 사용한 이번 버전에서는 리턴타입을 타입 추론을 이용하게 하고 단일 표현식 함수 문법을 사용했습니다.

 

바로 여기서 사용된 when 이 표현식으로써 사용된 것입니다. 함수에 의해서 리턴되는 값은 when 안의 하나의 표현식에서 나온 값입니다. 눈에 띄는 변화로는 if 문을 사용할 때 일일이 나열되었던 return 이라는 키워드가 사라졌습니다. (코틀린에서는 if 문도 표현식이라서 if-else if 로 구성한다면 return 키워드를 사용하지 않을 수 있긴 합니다.)

 

물론 두 버전의 isPromotionTarget() 함수는 동일한 입력으로 동일한 결과를 만들어냅니다. 하지만 when 을 사용한 버전은 비교적(?) 더 나은 가독성과 유지보수성을 제공합니다. 이처럼 when 은 일반적으로 if 에 비해서 간결합니다.

 

* P.S. 여기서 함정은 위 예시는 사실 if 도 when 도 사용하지 않고 간단히 (addToCart && rates == 2 || rates == 3) 조건식으로 해결할 수 있습니다. 예시를 위해 위처럼 구성한 것이니 when 의 특징에 초점을 두시면 되겠습니다 :)

 

여기서 중요한 포인트 하나가 등장합니다!

 

코틀린 컴파일러는 when 이 표현식으로 사용 될 때 else 부분이 존재하는지, 표현식이 가능한 모든 입력에 대해 값을 생성하는지 검증합니다.

즉, when 을 표현식으로 사용했는데 else 가 없거나 처리할 수 없는 입력 케이스가 있다면 컴파일러는 오류를 발생시킵니다.

 

이 컴파일 타임 체크는 코드의 정확성과 실수로 인해서 간과한 상황에 의한 오류를 줄이는 데 크게 기여해줍니다!

 

위 예제에서는 when 을 사용할 때 인자(argument) 를 받지 않았는데요. 이번에는 when 에 인자를 전달하는 예시를 살펴보겠습니다.

 

fun whatToDo(dayOfWeek: Any) = when (dayOfWeek) {
    "Saturday", "Sunday" -> "Relax"
    in listOf("Monday", "Tuesday", "Wednesday", "Thursday") -> "Work hard"
    in 2..4 -> "Work hard"
    "Friday" -> "Party"
    is String -> "What?"
    else -> "No Clue"
}
println(whatToDo("Sunday")) // Relax
println(whatToDo("Wednesday")) // Work hard
println(whatToDo(3)) // Work hard
println(whatToDo("Friday")) // Party
println(whatToDo("Munday")) // What?
println(whatToDo(8)) // No Clue

 

이번 예제에서는 when 에 dayOfWeek 이라는 변수를 넘겼습니다. when 안의 모든 조건이 논리 연산자로 이루어진 표현식이었던 이전 예제와는 다르게 이번 예제에서는 예제 안의 조건들이 혼합되어 있습니다.

 

우선 한 눈에 살펴봐도 

 

when 안의 첫 번째 라인에는 전달 받은 값이 콤마로 구분된 두 개의 값 중 하나에 해당하는지 확인합니다. 이는 자바에서는 제공하지 않는 문법이기 때문에 눈여겨 보실 필요가 있겠습니다.

 

다음 두 라인(in listOf(...), in 2..4) 은 전달 받은 파라미터가 리스트 안(또는 범위)에 속하는지를 각각 확인합니다.

 

다음은 dayOfWeek 값이 "Friday" 와 정확히 일치하는지 확인합니다.

 

다음은 전달 받은 값에 대해 타입 체크를 하는 부분입니다. is 라는 키워드는 스마트 캐스팅 기능을 제공하며, 관련해서 다룬 포스팅이 있으니 참고하시면 되겠습니다.

 

마지막으로 else 는 기본 선택지를 담당합니다.

else 는 위에 설정된 모든 조건에 해당하지 않을 경우에 해당하며, 컴파일러는 else 가 있는지를 강력하게 확인하고, else 가 마지막이 아닌 부분에 오는 것은 허용하지 않습니다.

 

when 의 각 조건 케이스에 뒤에는 블록{} 이 올 수도 있습니다. 그리고 블록 안의 마지막 표현식이 해당 조건의 결과로 리턴됩니다. 하지만 가독성 측면으로 봤을 때 블록을 이용하지 않는 것이 좋기 때문에 복잡한 로직이 필요하다면 리팩토링을 통해서 함수나 메소드로 분리하고 -> 뒤에서 해당 함수나 메소드를 호출하는 식으로 구현할 것을 권장합니다.

 

명령문으로써의 when

하나 이상의 값에 기반해서 다른 동작을 원한다면 when 을 표현식이 아니라 명령문으로 사용하면 됩니다. 이전 예제를 변경하여 스트링을 리턴하지 말고 출력해보도록 하겠습니다.

 

fun printWhatToDo(dayOfWeek: Any) {
    when (dayOfWeek) {
        "Saturday", "Sunday" -> println("Relax")
        in listOf("Monday", "Tuesday", "Wednesday", "Thursday") -> println("Work hard")
        in 2..4 -> println("Work hard")
        "Friday" -> println("Party")
        is String -> println("What?")
    }
}
printWhatToDo("Sunday") // Relax
printWhatToDo("Wednesday") // Work hard
printWhatToDo(3) // Work hard
printWhatToDo("Friday") // Party
printWhatToDo("Munday") // What?
printWhatToDo(8) //

 

위 예제에서 printWhatToDo() 함수의 리턴 타입은 Unit 입니다. 즉, 함수는 아무것도 리턴하지 않고 when 안에서 각각의 조건별로 프린트를 합니다. 앞서 살펴본 예제와 차이점이 있다면 else 문이 제거 되었습니다. 

 

when 을 표현식으로 사용할 때는 else 가 없을 경우 컴파일러가 에러 처리를 했었는데요.

코틀린에서 when 이 명령문으로 사용될 때는 else 가 없어도 상관 없습니다.

 

when 과 변수 스코프

위 예제들에서는 when 명령문 혹은 표현식의 외부에서 변수를 전달 받아 조건 확인을 위해 사용되었습니다. 그러나 꼭 그래야만 하는 것은 아닙니다. 변수가 when 의 스코프에서만 사용된다면 when 으로 변수의 스코프에 제약을 주는 것이 변수가 새어나가서 다르게 사용되는 것을 방지할 수 있고 코드를 관리하기 쉽게 만들어줍니다.

 

다음은 when 을 이용해서 시스템의 코어 숫자를 세는 함수입니다.

 

fun systemInfo(): String {
    val numberOfCores = Runtime.getRuntime().availableProcessors()
    return when (numberOfCores) {
        1 -> "1 core, packing this one to the museum"
        in 2..16 -> "You hav $numberOfCores cores"
        else -> "$numberOfCores cores!, I want your machine"
    }
}

 

systemInf() 함수는 코어의 숫자를 리턴합니다. 위 예제에서 보시다시피 numberOfCores 라는 변수를 when 외부에서 선언하여 when 에 전달하고  있는데요. 이를 다시 작성하여 노이즈를 줄이고 numberOfCores 변수의 스코프를 when 으로 제한해보도록 하겠습니다.

 

fun systemInfo(): String =
    when (val numberOfCores = Runtime.getRuntime().availableProcessors()) {
        1 -> "1 core, packing this one to the museum"
        in 2..16 -> "You hav $numberOfCores cores"
        else -> "$numberOfCores cores!, I want your machine"
    }

이렇게 리팩토링을 하게 되면 어떤 장점이 있을까요?

첫 번째, when 의 결과를 바로 리턴하여 바깥쪽 블럭{} 과 return 키워드를 제거해 덜 복잡한 짧은 코드를 만들 수 있습니다.

둘 째로 numberOfCores 는 when 의 결과를 얻을 때만 사용 가능하고, 이후의 연산에서는 사용이 불가능합니다.

이렇듯 변수의 스코프를 제한하는 것은 when 에서의 경우 뿐만 아니라 대체로 좋은 디자인입니다.

 

정리

기존에 다른 프로그래밍 언어(JAVA, C, C++ 등)에 어느정도 지식이 있는 채로 코틀린을 처음 학습 하시는 분들은 when 이 switch 에 대치되는 문법으로만 알고 넘어가기가 쉬워 몇 가지 디테일한 특성들을 놓치기가 쉬운데요. 코틀린은 객체지향 언어 뿐만 아니라, 함수 지향, 스크립트, DSL 지원 등 정말 많은 관점에서 많은 기능들을 제공하고 있기 때문에 기본기를 다지실 때 놓치고 있는 부분은 없나 잘 확인하면서 학습하는 것이 중요해 보입니다.

반응형
반응형