본문 바로가기

Reading Record/이펙티브 코틀린

[이펙티브 코틀린] Item3. 최대한 플랫폼 타입을 사용하지 마라

  • 코틀린은 null-safety 매커니즘으로 인해 NPE를 거의 찾아보기 힘듬
  • null-safety 매커니즘이 없는 자바, C 등의 프로그래밍 언어와 코틀린을 연결해서 사용할 때는 NPE 예외가 발생할 수 있음
public class JavaTest{ 
    public String giveName() { ... }
}
  • 위 자바 코드로 반환된 타입을 사용할때에 @Nullable 어노테이션이 붙어 있다면 nullable로 추정하고 String?으로 변경하면 되는데 만약 붙어 있지 않다면 자바에서 모든 것이 nullable일 수 있으므로 최대한 안전하게 접근하기 위해 nullable로 가정하고 접근해야 함

제네릭 타입

public class UserRepo {
    public List<User> getUsers() { ...}
}

val users: List<User> = UserRepo().users!!.filterNotNull()
  • 코틀린이 디폴트로 모든 타입을 nullable로 다룬다면, 이를 사용할 때 이러한 리스트와 리스트 내부의 User 객체들이 널 아니라는 것을 알아야 함
  • 그래서 코틀린은 자바 등의 다른 프로그래밍 언어에서 넘어온 타입들을 특수하게 다루고 이러한 타입을 플랫폼 타입이라고 부름
val repo = UserRepo()
val user1 = repo.user // user1의 타입 User!
val user2: User = repo.user // User
val user3: User? = repo     // User?

val users: List<User> = UserRepo().users
val users: List<List<User>> = UserRepo().groupedUsers
  • 코틀린에서는 플랫폼 타입은 타입 뒤에 ! 기호를 붙여서 표기함
  • 그러나 문제는 null이 아니라고 생각되는 것이 null일 가능성이 있으므로 여전히 위험하기 때문에 항상 주의를 기울여야 하고 설계자가 명시적으로 어노테이션으로 표기하거나 주석으로 달아두어야함

어노테이션 지원 목록

  • JetBrains의  @Nullable @NotNull
  • 안드로이드 @Nullable @NonNull
  • JSR-305( @Nullable, @CheckForNull @Nonnull
  • JavaX( @Nullable@CheckForNull@Nonnulljavax.annotation
  • FindBugs( @Nullable, @CheckForNull, @PossiblyNull @NonNull
  • ReactiveX( @Nullable @NonNull
  • 이클립스 ( @Nullable @NonNull
  • 롬복 ( @NonNull
  • 또는 JSR 305의 @ParametersAreNonnullByDefault주석을 사용하여 기본적으로 모든 유형이 Notnull이어야 함을 Java에서 지정할 수 있습니다 .

플랫폼 타입 사용의 문제점

public class JavaClass {
    public String getValue() {
        return null;
    }
}

fun staredType() {
    val value: String = JavaClass().value
    println(value.length)
}

fun platformType() {
    val value = JavaClass().value
    println(value.length)
}

  • 코틀린에서도 위와 같이 플랫폼 코드를 사용할 수 있으나 플랫폼 타입은 안전하지 않으므로 빨리 제거하는 것이 좋음
  • 위 stratedType, platformType 모두 null을 리턴한다고 가정하지 않으면 NPE가 발생
    • startedType의 경우 자바에서 가져오는 값을 가져오는 위치에서 NPE 발생
      • null이 아니라고 예상했지만 null이 나와 가져오는 지점에서 발생
    • platformType은 값을 활용할때 NPE 발생
      • 플랫폼 타입으로 지정된 변수는 nullable일 수도 있고, 아닐 수도 있어서 실제로 활용하는 라인에서 발생
interface UserRepo {
    fun getUserName() = JavaClass().value
}

class UserRepoImp: UserRepo {
    override fun getUserName():String? {
        return null
    }
}

fun main() {
    val repo: UserRepo = UserRepoImp()
    val text: String = repo.getUserName()
    println("User name length is ${text.length}")
}
  • 위 인터페이스 메소드의 inferred 타입이 플랫폼 타입이므로 누구나 nullable여부를 지정할 수 있는데 사용하는 쪽에서 nullable이 아니라고 받아들였다면 문제가 발생
  • 즉 플랫폼 타입이 전파되는 일은 굉장히 위험하고 가급적 제거하는 것이 좋음

정리

  • 다른 프로그래밍 언어에서 와서 nullable 여부를 알 수 없는 타입을 플랫폼 타입이라고 함
  • 플랫폼 타입은 사용하는 코드말고도 활용하는 곳까지 영향을 줄 수 있으므로 가급적 제거하는 것이 좋음
  • 혹은 사용하게 되었을때는 nullable 여부를 지정하는 어노테이션을 활용하는 것이 좋음