[이펙티브 코틀린] 1장 안정성 Item 3 최대한 플랫폼 타입을 사용하지 말라

Sdoubleu·2023년 1월 4일
0

이펙코

목록 보기
3/7
post-thumbnail

널 안정성(null-safety)은 코틀린의 주요 기능 중 하나

Q: 자바에서 String 타입을 리턴하는 메서드가 있다고 가정 코틀린에선 어떻게 처리?

A:
@Nullable 어노테이션이 붙어 있다면, 이를 nullable로 추정 -> String?으로 변경
@NotNull 어노테이션이 붙어 있다면 -> String으로 변경

If 다음과 같이 어노테이션이 붙어 있지 않다면?

//자바
public class JavaTest {
	public String giveName() {
    	//...
	}
}
  • 자바에서는 모든 것이 nullable일 수 있으므로 최대한 안전하게 접근한다면
    -> 이를 nullable로 가정하고 다뤄야 함

  • 🔥하지만, 어떤 메서드에서는 null을 리턴하지 않을 것이 확실하다면
    -> 마지막에 not-null 단정을 나타내는 !!을 붙임

⚡ nullable과 관련하여 자주 문제가 되는 부분은 자바의 제네릭 타입


자바 API에서 List<User>를 리턴하고, 어노테이션이 따로 붙어 있지 않은 경우엔??

코틀린이 디폴트로 모든 타입을 nullable로 다룬다면
-> 이를 사용할 때 리스트와 리스트 내부의 User 객체들이 널이 아니라는 것을 알아야 함

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

↪ 함수가 List<List<User>>를 리턴한다면 ? -> 복잡해짐..

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

리스트는 적어도 mapfilterNotNull 등의 메서드를 제공 !
다른 제네릭 타입이라면, 널을 확인하는 것 자체가 복잡한 일이 됨 ...

그래서 코틀린은 다른 프로그래밍 언어에서 넘어온 타입들을 특수하게 다룸
-> 이러한 타입을 플랫폼 타입(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일 가능성이므로, 여전히 위험💣
함수가 지금 당장 null을 return하지 않아도, 미래에는 변경될 수 있다는점 인지!

자바와 코틀린을 함께 사용중일 때,
자바 코드를 직접 조작할 수 있다면 가능한 @Nullable@NotNull 어노테이션 붙여서 사용권장🔥


ex) 자바->코틀린 플랫폼타입의 위험성 예시

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

// 코틀린
fun statedType() {
	val value: String = JavaClass().value
	//...
	println(value.length)
}

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

두 가지 모두 NPE(Null Pointer Exception)가 발생
두 코드는 오류의 발생 위치에 차이 존재

  1. statedType에서는 자바에서 값을 가져오는 위치에서 NPE가 발생
    오류가 발생 하면, null이 아니라고 예상을 했지만 null이 나옴

  2. platformType에서는 값을 활용할 때 NPE가 발생

    // 자바
    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
    }
  • 이처럼 플랫폼 타입은 많은 위험성을 가짐

➕ 인터페이스에서 플랫폼 타입을 사용한 예시

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

↪ 메서드의 inferred 타입(추론된 타입)이 플랫폼 타입
이는 누구나 nullable 여부를 지정할 수 있음

ex) 어떤 사람이 이를 활용해서 nullable을 리턴하게 했는데, 사용하는 사람이 nullable이 아닐거라고 받아들였다면 문제됨...

  class RepoImpl: UserRepo {
  	override fun getUserName(): String? {
  		return null
  	}
  }
  fun main() {
  	val repo: UserRepo = RepoImpl()
  	val text: String = repo.getUserName() // 런타임 때 NPE
  	print("User name length is ${text.legth}")
  }

⭐정리

다른 프로그래밍 언어에서 와서 nullable 여부를 알 수 없는 타입을 플랫폼 타입이라고 부름
이러한 플랫폼 타입을 사용하는 코드는 위험한 코드💣
-> 빨리 제거 필요

➕ 연결되어 있는 자바 생성자, 메서드, 필드에 nullable 여부를 지정하는 어노테이션을 활용하는 것도 좋음👍

profile
개발자희망자

0개의 댓글