이펙티브 코틀린 1장 아이템4 - inferred 타입으로 리턴하지 말자
요약
값을 리턴하는 함수를 만들때는 반드시 타입을 명시해야 한다
타입 추론
- 코틀린에서는 타입을 명시하지 않아도 변수가 선언될때 어떤 타입의 변수인지 추론해서 변수 선언이 가능하다
덕분에 코드량을 줄일 수 있다
추론된 타입 특징
(inferred타입 = 추론된 타입 이라는뜻이다)
1. 추론된 타입은 정확히 오른쪽에 있는 피연산자에 맞게 설정된다
open class Animal
class Zebra: Animal()
fun main() {
var animal = Zebra()
animal = Animal() // 오류 발생 타입 안맞음
}
위 코드를 보면 animal에 타입을 명시하지 않았다. 하지만 초기화할때 Zebra() 로 Zebra 타입을 넣어서 추론때문에 animal 의 타입은 Zebra 클래스이다
Zebra는 Animal을 상속받았다. animal 에 Animal 클래스를 넣으려 하니 오류가 생겼다. 업캐스팅이 안된다
open class Drinks{
var name :String = "음료수"
open fun Drink(){
println("${name}을 마십니다.")
}
}
class Cola : Drinks(){
var type :String = "콜라"
override fun Drink(){
println("${name}인 ${type}을 마십니다.")
}
fun Taste(){
println("${type}향이 납니다.")
}
}
fun main(){
var a: Drinks = Cola()
a = Drinks()
a.Drink()
}
위 코드처럼 하위 클래스로 명시한 변수에 상위 클래스를 넣는것을 업캐스팅이라고 한다. 그리고 위 코드는 잘 돌아간다
하지만
이렇게 타입 추론으로 변수를 만들어놓으면 그 변수는 업캐스팅이 안된다는것이다~
물론 이런 경우에는 변수를 만들때 타입을 명시하면 된다. 하지만 직접 명시를 못하는 상황(라이브러리 사용 등) 에서는 이런 문제가 간단히 해결되지 않는다
추론된 타입을 노출할때 생기는 문제
open class Car()
class NormalCar(): Car()
class ElectricCar(): Car()
val DEFAULT_CAR: Car = NormalCar()
interface CarFactory {
fun produce(): Car = DEFAULT_CAR
}
fun main(){
val carFactory = object: CarFactory {
override fun produce(): Car {
return super.produce()
}
}
val electricCarFactory = object: CarFactory {
override fun produce(): ElectricCar {
return ElectricCar()
}
}
var normalCar: Car = carFactory.produce()
var electricCar: ElectricCar = electricCarFactory.produce()
}
내가 자동차 생성 라이브러리를 만드는 개발자라고 가정하고 위와같은 코드를 만들었다고 하자
Car 클래스를 상속받는 NormalCar, ElectricCar 클래스 두개가 있고 차를 만들어주는 인터페이스를 만들었다고 치자
그 인터페이스의 produce는 기본적으로 NormalCar 인 DEFAULT_CAR 를 리턴한다
이렇게 만들어놓은 라이브러리를 사용자는 가져다 쓰면서 차도 만들고~ 전기차도 만들고~ 아주 행복한 시간을 보내고 있었다..
어느날 리팩토링을 하고싶어진 나..
val DEFAULT_CAR: Car = NormalCar()
interface CarFactory {
fun produce() = DEFAULT_CAR
}
어짜피 DEFAULT_CAR 에다가 Car로 명시했으니깐 ㅎㅎ produce에 명시해놓은 부분 지워야징 ㅋㅋ 해서 지움
그러다가 내가 퇴사함
그리고 그 자리에 상속? 이런거 모르는 사람이 들어왔다고 치자 혹은 새로 온 사람이 코드 숙지가 안됐다고 치자
val DEFAULT_CAR = NormalCar()
interface CarFactory {
fun produce() = DEFAULT_CAR
}
새로 들어온 사람이 ElectricCar 를 모르고 그냥
음.. DEFAULT_CAR 에 굳이 타입 명시를 해야되낭? NormalCar 리턴하는 그런거네 그냥~ 이러고 DEFAULT_CAR 에 명시해놓은 부분마저 지웠다
이렇게되면 해피해피해피~ 전기차 만들던 라이브러리 사용자가 띠용~!! ElectricCar, Car 둘다 못만들고 오직 NormalCar만 만들수있게 된다
이유는 위에서말한 타입추론 특징 때문!
타입 추론으로 변수를 초기화하면 그 변수에는 무조건 그 타입만 들어갈 수 있고 부모클래스 타입은 들어가지 못함
결론
- 타입을 확실히 지정해야 하는 경우에 명시적으로 타입을 적어주자
- 외부 API를 만들땐 쓰는 사람들을 위해서 타입을 명시하자
- 기존 코드에 있는 타입 명시를 함부로 지우지 말자
- 추론된 타입은 처음 만들어질땐 괜찮을수있지만 차후에 수정이 이루어질때 문제를 일으킬수있다
- 참고 사이트
https://velog.io/@ysh096/%EC%BD%94%ED%8B%80%EB%A6%B0-casting-is-as
https://augustin26.tistory.com/m/27#google_vignette
https://bottom-to-top.tistory.com/66?category=1027392