Kotlin Koans - Introduction / Nothing type

이준영·2020년 12월 10일
1

Kotlin Koans

목록 보기
8/9
post-thumbnail

문제

import java.lang.IllegalArgumentException

fun failWithWrongAge(age: Int?)    {
    throw IllegalArgumentException("Wrong age: $age")
}

fun checkAge(age: Int?) {
    if (age == null || age !in 0..150) failWithWrongAge(age)
    println("Congrats! Next year you'll be ${age + 1}.")
}

fun main() {
    checkAge(10)
}

Nothing type can be used as a return type for a function that always throws an exception. When you call such a function, the compiler uses the information that it throws an exception.

Specify Nothing return type for the failWithWrongAge function. Note that without the Nothing type the checkAge function doesn't compile because the compiler assumes the age can be null.

풀이

import java.lang.IllegalArgumentException

fun failWithWrongAge(age: Int?): Nothing {
    throw IllegalArgumentException("Wrong age: $age")
}

fun checkAge(age: Int?) {
    if (age == null || age !in 0..150) failWithWrongAge(age)
    println("Congrats! Next year you'll be ${age + 1}.")
}

fun main() {
    checkAge(10)
}

이번 문제는 Nothing 타입을 사용하여 컴파일 에러를 해결하는 문제였습니다.

Nothing 타입은 반환되지 않는 코드이거나 또는 예외를 던지는 코드에서 사용됩니다. 이를 좀 더 추상화하여 말해보면 절대 도달할 수 없는 코드의 위치를 표시하는데 사용됩니다.

이게 무슨 말이야?

라고 생각하실 수 있는데, 간단한 예시를 통해 알아보겠습니다.

fun throwIllegalArgumentException(): Nothing {
    throw IllegalArgumentException()
}

fun main() {
    throwIllegalArgumentException() // 이 코드 이후는 도달할 수 없는 코드이다
    val a: String = "Hello"
}

위 코드에서 Nothing 반환 타입을 가지고 있는 throwIllegalArgumentException() 함수가 호출된 이후의 다른 코드들은 도달할 수 없음을 알 수 있습니다. 왜냐하면 실행되기 이전에 예외가 발생하기 때문이죠. Nothing 반환 타입을 가지고 있는 함수가 호출되는 위치는 곧 절대 도달할 수 없는 코드의 시작이라고 볼 수 있습니다.

그럼 다시 돌아가서 문제를 보겠습니다.

문제에서 주어진 코드는 checkAge() 함수에서 컴파일 에러가 발생합니다.

fun checkAge(age: Int?) {
    if (age == null || age !in 0..150) failWithWrongAge(age)
    println("Congrats! Next year you'll be ${age + 1}.")  // age 변수가 null이 아님을 보장할 수 없다. 컴파일 에러!
}
/* kotlin.Int */
public final operator fun plus(
    other: Int
): Int

Kotlin의 plus 연산자는 null 일 수 있는 값은 허용하지 않습니다. 그러나 age 변수가 null 일 경우, 위의 if 표현식에서 null 인지 여부를 체크하는 조건이 있지만 failWithWrongAge() 함수만 실행되고 다음 라인으로 넘어갈 수 있습니다.

그렇다면 만약 age 변수가 null 일 경우에 failWithWrongAge() 함수가 return 과 같은 행위를 하는 함수면 어떻게 될까요? failWithWrongAge() 함수가 실행되는 위치가 곧, 더는 코드가 도달할 수 없는 위치를 표현할 수 있다면 이 문제를 해결할 수 있을 것입니다.

failWithWrongAge() 함수의 반환 타입을 Nothing 으로 정의하면 age 변수가 null 일 경우 if 표현식에서 예외가 발생하고 다음 코드로 넘어갈 수 없게 됩니다. 그리고 이는 다음 라인에서 사용될 age 변수는 null 이 아님을 보장해주기 때문에 컴파일러는 더 이상 해당 코드에서 에러를 발생시키지 않습니다.

학습 내용

Exception

The Nothing type

throw 는 Kotlin에서 표현식이기 때문에 (예를 들어) elvis 표현식의 일부로 사용할 수 있습니다:

val s = person.name ?: throw IllegalArgumentException("Name required")

throw 표현식의 타입은 Nothing 이라는 특별한 타입입니다. 이 타입은 값을 가지지 않고, 절대 도달할 수 없는 코드의 위치를 표시하는데 사용됩니다. 코드에서 절대 반환되지 않는 함수를 표시하는데 Nothing 을 사용할 수 있습니다:

fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}

풀이에서 말한 것처럼 절대 도달할 수 없는 위치는 while 문의 조건이 true 인 함수나 throw 표현식이 사용된 함수가 호출된 위치를 말합니다. 즉, 이 함수가 호출되는 위치가 결국 절대 도달할 수 없는 위치이고, Nothing 타입은 이 위치를 표시하는 타입이라고 생각할 수 있습니다.

아래 함수를 호출했을때, 컴파일러는 해당 함수를 호출한 위치 이후로 코드가 더 이상 실행되지 않는다는 것을 알고 있습니다:

val s = person.name ?: fail("Name required")
println(s)     // 's' is known to be initialized at this point

정리하며

Kotlin에서 Nothing 타입은 더 이상 코드가 도달하지 못하는 위치를 표현할 때 사용됩니다. 이 타입을 잘 활용하면 컴파일러가 코드를 더 잘 분석하고 안전하게 코드가 실행되도록 도울 수 있습니다.

Kotlin에서 null 을 안전하게 사용할 수 있는 만큼, 어떻게 다루는 지에 대해서도 잘 학습하면 좋을 것 같습니다.

참고 자료

Kotlin Reference - More Language Constructs / Exceptions

[Kotlin] 헷갈리는 "Nothing" 확실하게 이해하기(feat. Any, Unit) - 준비된 개발자

profile
growing up 🐥

0개의 댓글