ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 아이템3 - 최대한 플랫폼 타입을 사용하지 말라
    Kotlin/Effective Kotlin 요약 2022. 12. 31. 00:01
    728x90

    개요

    코틀린에서 null-safety는 주요 기능 중 하나입니다.

    만약 자바에서 String 타입을 리턴하는 메서드가 존재하고 코틀린에서 이를 사용하려면 어떻게 해야 할까요?

    @Nullable, @NotNull 어노테이션이 붙어있다면 해당 프로퍼티에 맞게 변환해주면 됩니다.

    하지만 어노테이션이 붙어있지 않는다면 안전하게 nullable으로 가정해야 합니다.

     

    하지만 어떤 메서드는 null을 리턴하지 않을 것이 확실할 수 있습니다.

    이런 경우에는 not-null을 나타내는 !!를 붙입니다.

     

    이때 자주 문제가 되는 부분이 자바의 제네릭 타입입니다.

     

    코틀린과 자바의 제네릭 타입

    코틀린이 디폴트로 모든 타입을 nullable로 다룬다면, 리스트 자체만 널 인지 확인해서는 안 되고, 그 내부에 있는 것들도 널 인지 확인해야 합니다.

     

    //자바
    public class UserRepo{
        public List<User> getUsers(){
    
        }
    }
    
    //코틀린
    fun main() {
        val users: List<User> =  UserRepo.users!!.filterNotNull()
    }

     

    조금 더 나아가서 List<List<User>>를 반환한다면?

    val users: List<List<User>> = UserRepo.groupedUsers!!.map {it!!.filterNotNull()}

    리스트는 적어도 map와 filterNotNull 등의 메서드를 제공하지만 다른 제네릭 타입이라면 null을 확인하는 것 자체가 복잡한 일이 됩니다.

     

    플랫폼 타입

    코틀린은 자바 등의 다른 프로그래밍 언어에서 넘어온 타입들을 플랫폼 타입이라고 부릅니다.

    String! 처럼 이름 뒤에 ! 기호를 붙여서 표기합니다.

     

    직접적인 코드에는 나타나지 않지만 이를 선택적으로 사용합니다.

    val repo = UserRepo()
    val user1 = repo.user //user1의 타입은 User!
    val user2: User = repo.user //user2의 타입은 User
    val user3: User? = repo.user //user3의 타입은 User?

     

    하지만 null이 아니라고 생각되는 것이 null일 가능성이 존재하여 여전히 위협적입니다.

     

    따라서 자바를 코틀린과 함께 사용할 때, 자바 코드를 직접 조작할 수 있다면 가능한 @Nullable, @NotNull 어노테이션을 붙여서 사용하기 권장됩니다.

    statedType VS platformType

     

    //자바
    public class JavaClass{
    	public String getValue(){
    		return null;
    	}
    }
    
    //코틀린
    fun statedType(){
    	val value: String = JavaClass().value //NPE
    	println(value.length) 
    }
    
    fun platformType(){
    	val value = JavaClass().value
    	println(value.length) //NPE
    }

     

    두 가지 모두 NPE가 발생합니다.

     

    statedType에서는 자바에서 값을 가져오는 위치에서 NPE가 발생합니다.

    따라서 코드를 굉장히 쉽게 수정할 수 있습니다.

     

    platformType에서는 값을 활용할 때 NPE가 발생합니다.

    따라서 오류를 찾는데 더 오랜 시간이 걸릴 수 있습니다.

     

    플랫폼 타입의 더 많은 위험성

    인터페이스에서 플랫폼 타입을 사용하고 있습니다.

    interface UserRepo{
    	fun getUserName() = JavaClass().value
    }

     

    이런 경우 메서드의 inferred 타입이 플랫폼 타입이며 누구나 nullable을 지정할 수 있게 됩니다.

     

    어떤 사람이 상속을 받아 nullable을 리턴하게 됐는데, 사용하는 사람이 nullable이 아닐 거라고 받아들였다면 문제가 발생합니다.

    class RepoImpl: UserRepo{
    	override fun getUserName(): Stirng?{
    		return null
    	}
    }
    
    fun main(){
    	val repo: UserRepo = RepoImpl()
    	val text: STring = repo.getUserName() //Runtime 시 NPE
    }

    다행히 인텔리제이에서는 경고를 출력해줍니다.

     

     

    정리

    다른 프로그래밍 언어에서 nullable 여부를 알 수 없는 타입을 플랫폼 타입이라고 합니다.

    이런 플랫폼 타입은 해당 코드뿐만 아니라 활용하는 곳까지 영향을 줄 수 있는 위험한 코드입니다.

    따라서 이런 플랫폼 타입을 활용하기 보다는 @NotNull, @Nullable 어노테이션을 활용하는 것이 권장됩니다.

     

    댓글

Designed by Tistory.