Better Kotlin — 플랫폼 타입

Platform Type 은 없어져야 마땅합니다.

Jaeho Choe
5 min readMar 4, 2021

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()
}

위 코드에서 AccountmName 이 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이 코드에서 퍼져나가지 않도록 해주는 것이 중요합니다.

참고 자료:
https://leanpub.com/effectivekotlin

--

--

Jaeho Choe
Jaeho Choe

No responses yet