공부/Kotlin

이펙티브 코틀린 1장 아이템4 - inferred 타입으로 리턴하지 말자

데자와 맛있다 2024. 7. 6. 21:00

요약

값을 리턴하는 함수를 만들때는 반드시 타입을 명시해야 한다

 

타입 추론

- 코틀린에서는 타입을 명시하지 않아도 변수가 선언될때 어떤 타입의 변수인지 추론해서 변수 선언이 가능하다

덕분에 코드량을 줄일 수 있다

 

추론된 타입 특징

(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

 

코틀린 casting, is, as

클래스를 구현할 때 캐스팅이라는 개념이 있다.https://m.blog.naver.com/wnstn0154/221855117820https://altongmon.tistory.com/601업 캐스팅은 하위 클래스가 상위 클래스화 되는 것위의 코드에서는

velog.io

https://augustin26.tistory.com/m/27#google_vignette

 

[이펙티브 코틀린] Item4. inferred 타입으로 리턴하지 말라

코틀린의 타입 추론(type inference)은 널리 알려진 코틀린의 특징이며 자바도 자바10부터는 타입 추론을 도입했습니다. inferred 타입은 정확하게 오른쪽에 있는 피연산자에 맞게 설정되며 슈퍼클래

augustin26.tistory.com

https://bottom-to-top.tistory.com/66?category=1027392

 

아이템 4 - inferred 타입으로 리턴하지 말라

코틀린은 타입추론을 지원한다. 이 때 주의할 점이 있다. 먼저 추론된(inferred)타입은 정확하게 오른쪽에 있는 피연산자에 맞게 설정된다. 따라서 슈퍼클래스 혹은 인터페이스로 설정되지 않음을

bottom-to-top.tistory.com