[Effective Kotlin] 아이템 27 : 변화로부터 코드를 보호하려면 추상화를 사용하라

0

함수와 클래스 등의 추상화로 실질적인 코드를 숨기면, 사용자가 세부사항을 알지 못해도 괜찮다는 장점이 있다.
또한 함수의 입출력만 제대로 안다면, 수정또한 가능하다.

비유로 얘기하면, 자동차 제조업체와 엔지니어가 작동만된다면, 무엇을 바꿔도 바뀐지 전혀 모를것이다.

이번절에서는 추상화를 통해 변화로부터 코드를 보호하는 행위가 어떤 자유를 가져오는지 알아보자.

1. 상수

리터럴은 아무것도 설명하지 않는다.
따라서 반복적으로 등장할때, 문제가 된다.
이러한 리터럴을 상수프로퍼티로 변경하면 해당값에 의미있는 이름을 붙일수 있다.

fun isPassWordValid(text: String) : Boolean {
 if(text.length < 7) return false
 
}

여기서 숫자 7은 비밀번호의 최소길이를 나타내겠지만, 상수로 빼낸다면 훨씬 쉽게 이해할 수 있다.

fun isPasswordValid(text : String) : Boolean {
 if(text.length < MIN_PASSWORD_LENGTH) return false

}

이렇게 하면 비밀번호 최소 길이를 변경하기가 쉽다.
그래서 두번이상 사용되는 값은 상수로 추출하는 것이 좋다.

예를 들어 데이터베이스에 동시 연결을 할수있는 최대 스레드 수를 정했다.

val MAX_THREADS = 10

만약 이러한 숫자가 프로젝트 전체에 퍼져있다면 변경하기가 정말 힘들것이다.

상수로 추출한다면

  • 이름을 붙일수 있다.
  • 해당값을 쉽게 변경 가능하다.

2. 함수

어플리케이션을 개발하면서, 토스트 메세지를 자주 출력해야하는 상황이 발생한다고 쳐보자.

아래와 같은 확장함수를 만들수 있다.

fun Context.toast(
 message : String,
 duration : Int = Toast.LENGTH_LONG
){
 Toast.makeText(this, message, duration).show()
}

이렇게 일반적인 알고리즘을 추출하면 토스트를 출력하는 코드를 항상 기억해두지 않아도 괜찮다.

만약 토스트가 아니라 스낵바라는 다른 형태의 방식으로 출력한다면, 스낵바를 출력하는 확장함수로 만들고,
기존의 Context.tast()를 Context.snackbar()로 한꺼번에 수정하면 된다.

fun Context.snackbar(
 message :String,
 length : Int = Toast.LENGTH_LONG
){

}

하지만 이런 해결법은 매우 좋지않다.
다른 모듈이 이 함수에 의존한다면, 다른 모듈에 큰문제가 발생한다.
또한 파라미터는 한꺼번에 바꾸기가 쉽지않다.

만약 메세지의 출력방법이 바뀔수 있다는 것을 알고 있다면, 이때부터 중요한것은 사용자에게 메세지를 출력하고 싶다는 의도이다.

따라서 메세지를 출력하는 더 높은레벨의 함수로 옮기자.

fun Context.showMessage(
 message : String
 duration : MessageLength = MessageLength.Long
){
 val taostDuration - MessageLength.Long
  SHORT -> Length.LENGTH_SHORT
  LONG -> Length.LEGTH_LONG
  
 Toast.makeText(this, message, toastDuration).show()
}


enum class MessageLength{SHORT , LONG}

컴파일러 관점에서는 별다른 변경이 없다고 하지만, 사람의 관점에서 이름이 바뀌면 큰 변화가 일어난것이다.
함수는 추상화를 표현하는 수단이며, 함수 시그니처는 이 함수가 어떤 추상화를 표현하는지 알려준다.
따라서 이름은 굉장이 중요하다.

함수 시그니처를 변경하면 프로그램 전체에 큰 영향을 준다.
구현을 추상화할 수 있는 강력한 방법에는 클래스가 있다.

3. 클래스

이전 메시지 출력을 클래스로 추상화해보자.

 class MessageDisplay
 
fun showMessage(
 message : String
 duration : MessageLength = MessageLength.Long
){
 val taostDuration - MessageLength.Long
  SHORT -> Length.LENGTH_SHORT
  LONG -> Length.LEGTH_LONG
  
 Toast.makeText(this, message, toastDuration).show()
}

enum class MessageLength{SHORT , LONG}


//사용

val messageDisplay = MessageDisplay(context)
messageDisplay.show("Message")

클래스가 함수보다 더 강력한 이유는 상태를 가질수있고, 많은 함수를 가질수 있다는 점이다.
의존성 주입 프레임워크를 사용하여 클래스 생성을 위임 할수도 있으며, mock 객체를 활용하여 해당 클래스에 의존하는 다른 클래스의 기능을 테스트 할수도 있다.

게다가 메세지를 출력하는 다양한 종류의 메소드를 만들수도 있다.

messageDisplay.setChristmasMode(true)

이처럼 클래스는 훨씬 더많은 자유를 보장해준다. 하지만 여전히 한계가 있다.

클래스가 final이라면, 해당 클래스 타입 아래에 어떤 구현이 있는지 알수 있다.
open class를 활용하면 조금 더 자유로워 진다.

더많은 자유를 얻으려면 인터페이스 뒤에 클래스를 숨기는 방법을 통해 더 자유로워질 수 있다.

profile
쉽게 가르칠수 있도록 노력하자

0개의 댓글