본문 바로가기

Kotlin/안전성, 가독성을 효과적인 향상시키는 사용법

👋Kotlin : 3️⃣최대한 플랫폼 타입 사용을 제한하라

728x90

코틀린은 2010년에 처음 개발되었지만 2016년 2월에 첫 번째 안정 버전(stable version)이 공식적으로 배포되었을 정도로 굉장히 오랜 시간 동안 만들어지고 있고, 처음부터 대규모 애플리케이션을 실용적으로 만들기 위한 프로그래밍 언어로 설계되었고 현재는 모바일 애플리케이션, 웹 애플리케이션의 백엔드, 웹 애플리케이션의 프론트엔드 등 다양한 영역에서 활용되고 있습니다.

 

3️⃣ 플랫폼 타입 사용 제한

코틀린은 null-safety 매커니즘으로 NPE(Null Posinter Exception)을 찾아보기 힘들지만, null-safety 매커니즘이 없는 자바와 코틀린을 연결해서 사용할 때는 예외가 발생할 확률이 높아집니다.

 

자바에서 @Nullable Type은 nullable로 추정해 Type?으로 변경하고, @NotNull Type인 경우 Type으로 변경하면 됩니다. 만약 이러한 어노테이션이 없다면 안전성을 위해 nullable로 가정하고 다루어야 합니다.

 

자바 API에서 List<Food>를 리턴하고, 어노테이션이 따로 붙어 있지 않은 경우, 코틀린에서 모든 타입을 nullable로 다뤄야 하는데 리스트 자체와 리스트 내부의 Food 객체들이 null이 아닌지 확인해야 합니다.

그래서 코틀린은 다른 프로그래밍 언어에서 전달되어 nullable인지 아닌지 알 수 없는 타입을 플랫폼 타입(platform type)이라고 합니다.

 

플랫폼 타입은 String!과 같이 타입 뒤에 ! 기호(notation)가 붙지만, Notation이 직접적으로 코드에 나타나진 않습니다. 예제를 통해 확인해보겠습니다.

public class Food {
    public static String getName() {
        ...
    }
}

val foodName = Food.getName()		// type : String!
val foodName: String! = Food.getName()  // Error

val foodName: String = Food.getName()	// type : String
val foodName: String? = Food.getName()	// type : String

 

자바와 연결된 플랫폼 타입을 사용할 때는 null이 아니라고 생각되어도 null일 가능성이 있기 때문에 플랫폼 타입은 안전하지 않으므로, 최대한 제거하는 것이 좋지만, 어쩔 수 없다면 가능한 @Nullable과 @NotNull 어노테이션을 붙여서 사용하는 것이 좋습니다. 아래 예제를 통해 관련 내용을 확인해보겠습니다.

(JSR 305의 @ParametersAreNonnullByDefault 어노테이션을 활용하면 자바에서도 디폴트로 파라미터가 널이 아니라는 것을 보장)

public class Food {
	@NotNull
    public static String notNullGetName() {
        ...
    }
    
    @Nullable
    public static String nullableGetName() {
        ...
    }
}

val foodName = Food.notNullGetName()		// type : String
val foodName = Food.nullableGetName()		// type : String?

val foodName: String? = Food.notNullGetName()	// type : String?이지만 NotNull이므로 ? Notaion이 필요없음
val foodName: String = Food.nullableGetName()	// type : Error

=========================================================================================

public class Food {
    public static String getName() {
        return null;
    }
}

fun stringType() {
    val name: String = Food.name
    println(name.length)
}

fun platformType() {
    val name = Food.name
    println(name.length)
}

 

위 예제에서 두 메서드 모두 Exception이 발생하지만, stringType()에서는 타입을 String으로 선언했기 때문에 값을 초기화하는 위치에서 컴파일 에러인 IllegalStateException이 발생하지만, platformType()에서는 값을 활용할 때 런타임 에러인 NullPointerException이 발생하므로, 오류를 찾는데 오랜 시간이 걸릴 수 있고 잠재적으로 오류를 불러올 수 있으니 플랫폼 타입은 최대한 제거하는 것이 좋습니다.