안드로이드 개발자 노트
[이펙티브 코틀린] Item3. 최대한 플랫폼 타입을 사용하지 말라 본문
널 안정성(null-safety)은 코틀린의 주요 기능 중 하나입니다.
null-safety 메커니즘이 없는 자바, C등의 프로그래밍 언어와 코틀린을 연결해서 사용할 때는 널 포인터 예외(Null-Pointer Exception, NPE)가 발생할 수 있습니다.
@Nullable 어노테이션이나 @NotNull 어노테이션이 붙어있다면 코틀린에서 활용할때는 String?과 String으로 변경하면 됩니다.
자바에서 모든 것이 null일 수 있으므로 최대한 안전하게 접근한다면, 이를 nullable로 가정하고 다루어야 합니다.
하지만 메서드가 null을 리턴하지 않을 것이 확실한 경우 not-null 단정을 나타내는 !!를 붙입니다.
/ 자바
public class UserRepo{
public List<User> getUsers() {
//...
}
}
// 코틀린
val users: List<User> = UserRepo().users!!.fillterNotNull()
// List<List<User>>를 리턴한다면? 더 복잡해진다.
val users: List<List<User>> = UserRepo().groupedUsers!!.map { filterNotNull() }
코틀린은 자바 등의 다른 프로그래밍 언어에서 넘어온 타입들을 특수하게 다루며, 이러한 타입을 플랫폼 타입(platform type)이라고 부릅니다.
플랫폼 타입이란, 다른 프로그래밍 언어에서 전달되어서 nullable인지 아닌지 알 수 없는 타입을 말합니다.
플랫폼 타입은 String! 처럼 타입 이름 뒤에 ! 기호를 표기합니다.
// 자바
public class UserRepo {
public User getUser() {
//...
}
}
// 코틀린
val repo = UserRepo()
val user1 = repo.user // user1의 타입은 User!
val user2: User = repo.user // user2의 타입은 User
val user3: User? = repo.user // user3의 타입은 User?
val users: List<User> = UserRepo().users
val users: List<List<User>> = UserRepo().groupedUsers
그러나 여전히 null이 아니라고 생각되는 것이 null일 가능성이 있으므로 위험합니다.
자바 코드를 직접 조작할 수 있다면 가능한 @Nullable과 @NotNull 어노테이션을 붙여서 사용하면 좋습니다.
// 자바
import org.jetbrains.annotations.NotNull;
public class UserRepo {
public @NotNull User getUser() {
//...
}
}
결국에 플랫폼 타입은 안전하지 않으므로, 최대한 빨리 제거하는 것이 좋습니다.
왜 그런지 간단한 예로 statedType과 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(Null-Pointer Exception)가 발생합니다.
statedType에서는 자바에서 값을 가져오는 위치에서 NPE가 발생합니다.
이 위치에서 오류가 발생하면, null이 아니라고 예상을 했지만 null이 나온다는 것을 굉장히 쉽게 알 수 있습니다.
platformType에서는 값을 활용할 때 NPE가 발생합니다.
객체를 사용한다고 해서 NPE가 발생될 거라고 생각하지는 않으므로, 오류를 찾는데 굉장히 오랜 시간이 걸리게 될 것입니다.
추가적인 예로, 인터페이스에서 플랫폼 타입을 사용한다면 다음과 같은 문제가 발생합니다.
interface UserRepo {
fun getUserName() = JavaClass().value // inferred 타입(추론된 타입)
}
이 경우 플랫폼 타입은 inferred 타입(추론된 타입)이 되며, 이는 누구나 nullable 여부를 지정할 수 있다는 것이 됩니다.
어떤 사람이 이를 활용해서 nullable을 리턴하게 했는데 사용하는 사람이 nullable이 아닐거라고 받아들였다면(not-null일거라 생각했다면), 문제가 됩니다.
class RepoImpl: UserRepo { // A: nullable을 리턴하게 해야지
override fun getUserName(): String? {
return null
}
}
fun main() {
val repo: UserRepo = RepoImpl()
// B: not-null 이겠지?
val text: String = repo.getUserName() // 런타임 NPE
print("User name length is ${text.length}")
}
정리
- 다른 프로그래밍 언어에서 와서 nullable 여부를 알 수 없는 타입을 플랫폼 타입이라고 부른다.
- 이러한 플랫폼 타입을 사용하는 코드는 해당 부분만 위험할 뿐만 아니라, 이를 활용하는 곳까지 영향을 줄 수 있는 위험한 코드다.
- 이런 코드를 사용하고 있다면 빨리 해당 코드를 제거하는 것이 좋다.
- 연결되어 있는 자바 생성자, 메서드, 필드에 nullable 여부를 지정하는 어노테이션을 활용하는 것이 좋다.
'Kotlin > 이펙티브 코틀린' 카테고리의 다른 글
[이펙티브 코틀린] Item4. inferred 타입으로 리턴하지 말라 (0) | 2023.08.27 |
---|---|
[이펙티브 코틀린] Item2. 변수의 스코프를 최소화하라 (0) | 2023.08.19 |
[이펙티브 코틀린] Item1. 가변성을 제한하라 (0) | 2023.05.23 |