Better Kotlin — 플랫폼 타입
Platform Type 은 없어져야 마땅합니다.
Platform Type
새삼스럽지만 Kotlin의 null-safety 는 정말 사랑스럽습니다. Java 시절 Exception의 대명사였던 NPE (NullPointException) 는 Kotlin의 null-safety 를 만나 대부분 제거 되었습니다. 하지만 Kotlin과 Java 또는 C와 같이 null-safety가 없는 다른 언어와의 연결에서는 이런 메커니즘으로 NPE를 완벽하게 보호할 수 없습니다.
String
을 반환 타입으로 갖는 Java 함수가 있다고 가정해봅시다. Kotlin에서 해당 함수를 사용할때 어떤 유형으로 사용하게 될까요? Java 함수에 @Nullable
어노테이션이 선언 되어 있다면 String?
으로 취급할 것이고 Java 함수에 @NotNull
어노테이션이 선언 되어 있다면? String
으로 취급할 것입니다. 그리고 위와 같이 @Nullable
이나 @NotNull
과 같은 명시적으로 타입을 알려주는 어노테이션이 없는 경우 Kotlin은 해당 값이 nullable 인지 아닌지를 알 수 없습니다. 이런 특수한 유형을 Kotlin에서 Platform type 이라고 합니다.
Platform type은 String!
느낌표 하나로 표시됩니다. 이 표기법은 코드에서 명시적으로 사용할 수 없고 다만 이 값을 nullable 또는 non-null 유형으로 선택하여 사용할 수 있습니다. 이런 선택은 우리는 타 플랫폼에서 특별한 문제 없이 값을 가져 올 수 있도록 도와주지만 잘못된 선택(?)을 하게될 위험성을 항상 내포하고 있습니다. 우리가 null 이 아닐거라고 가정한 값이 null 여전히 null일 수 있다는 점이죠. 따라서 Java에서 Platform type을 가져올때는 매우매우 신중해야 합니다. 지금은 null을 반환하지 않을 함수라도 앞으로 영원히 null을 반환하지 않는다는 보증은 없습니다. 따라서 Java에서 @Nullable
이나 @NotNull
어노테이션을 도입하여 가능한 모든 Platform type을 제거하는 것이 중요합니다. 이런 어노테이션은 Platform type을 제거하여 안정성을 높일 뿐만 아니라 같은 Java 개발자에게도 중요한 정보가 됩니다. Kotlin이 Android의 주요한 언어가 된 이후 이런 어노테이션은 중요한 변경사항 중 하나였고 아래와 같이 다양한 어노테이션을 지원하고 있습니다.
- JetBrains(
@Nullable
,@NotNull
org.jetbrains.annotations) - Android (
@Nullable
,@NonNull
androidx.annotation) - JSR-305(
@Nullable
,@CheckForNull
@Nonnull
javax.annotation) - JavaX(
@Nullable
,@CheckForNull
,@Nonnull
javax.annotation) - FindBugs (
@Nullable
,@CheckForNull
,@PossiblyNull
,@NonNull
edu.umd.cs.findbugs.annotations) - ReactiveX(
@Nullable
,@NonNull
io.reactivex.annotations) - Eclipse(
@Nullable
,@NonNull
org.eclipse.jdt.annotation) - Lombok (
@NonNull
from lombok)
그럼 Kotlin 에서는 Java 개발자가 어노테이션을 추가해주길 기다려야만 할까요? 이미 지원이 끝나버린 Java 코드를 사용해야 하거나 아니면 다른 이유로 어쩔 수 없이 Platform type을 다뤄야할 경우가 있습니다. 이럴땐 안전을 위해 코드에서 가장 좁은 범위에서만 Platform type을 노출시켜야 합니다.
// in Java
public class Account {
String mName;
public String getName() {
return mName;
}
}// in Kotlin
fun badCase(): String {
val name = Account().name
return name.toUpperCase()
}fun betterCase(): String {
val name : String = Account().name
return name.toUpperCase()
}
위 코드에서 Account
의 mName
이 null 일 경우에 badCase()
함수와 betterCase()
함수는 모두 NullPointException이 발생합니다. 다른점은 어디서 발생하느냐 입니다. betterCase()
에서는 name
에 값을 할당하는 순간 Exception이 발생합니다. 개발자는 name
의 값을 String?
로 바꿔주는 등 Exception에 대한 원인파악과 처리를 하기 상대적으로 쉽습니다. 하지만 badCase()
는 name
을 실제로 non-null 값으로 사용할때 Exception
이 발생합니다. 위 코드의 경우 name.toUpperCase()
에서 바로 발생하겠지만 함수의 길이가 길거나 혹은 fun worstCase() = Account().name
이런 함수를 다른 코드에서 참조하여 사용할 경우 발생한 NPE
는 발생할 위치를 추적하는것이 매우 까다롭습니다. 따라서 Kotlin 에서 Platform type을 사용해야하는 경우가 있다면 값을 가져오는 시점에 타입을 명시하여 Platform type이 코드에서 퍼져나가지 않도록 해주는 것이 중요합니다.