ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 27장 - 변화로부터 코드를 보호하려면 추상화를 사용하라
    Kotlin/Effective Kotlin 요약 2023. 3. 13. 00:01
    728x90

    물 위를 걷는 것과 명세서로 소프트웨어를 개발하는 것은 쉽다. 둘 다 동결되어 있다면..

     

    함수와 클래스 등의 추상화로 실질적인 코드를 숨기면, 사용자가 세부 사항을 알지 못해도 괜찮다는 장점이 있습니다.

     

    추상화의 다양한 사례

    1단계 : 상수

    리터럴(매직넘버)은 아무것도 설명하지 않습니다.

    이를 상수로 빼어낸다면 훨씬 쉽게 이해할 수 있습니다.

    //지양
    if(text.length < 7) return false
    
    //지향
    const val MIN_PASSWORD_LENGTH = 7
    if(text.length < MIN_PASSWORD_LENGTH) return false

     

    2단계 : 함수

     

    토스트 메시지를 자주 출력해야 한다면 다음과 같은 코드를 작성할 수 있습니다.

    Toast.makeText(this, message, Toast.LENGTH_LONG).show()

    또한 이를 확장함수로 사용할 수도 있습니다.

     

    이제 토스트가 아니라 스낵바라는 다른 형태의 방식으로 출력해야 한다면 네이밍을 snackbar로 바꾸어 해결할 수 있습니다.

    하지만 함수의 이름을 직접 바꾸는 것은 위험할 수 있습니다. (다른 모듈이 이 함수를 의존하고 있을 수 있기 때문)

     

    메시지의 출력 방법이 바뀔 수 있다는 것을 알고 있다면, 이제 중요한 것은 사용자에게 메시지를 출력하고 싶다는 의도자체입니다.

     

    이를 showMessage라는 높은 레벨의 함수로 옮길 수 있습니다.

    fun Context.showMessage(
    	message: String,
    	duration: MessageLength = MessageLength.Long
    ){
    	val toastDuration = when(duration){
    		SHORT -> LENGTH.LENGTH_SHORT
    		LONG -> LENGTH.LENGTH_LONG
    	}
        Toast.makeText(this, message, toastDuration).show()
    }
    
    enum class MessageLength {SHORT, LONG}

     

    사실 큰 차이가 없다고 생각할 수 있지만 사람의 관점에서는 이름이 바뀌며 큰 변화가 일어난 것입니다.

     

    3단계 : 클래스

     

    클래스가 함수보다 더 강력한 이유는 상태를 가질 수 있으며, 많은 함수를 가질 수 있다는 점 때문입니다.

    상태를 가질 수 있으며, 많은 함수를 가질 수 있다는 점 때문입니다.

     

    의존성 주입 프레임워크를 사용하여, 클래스 생성을 위임할 수도 있으며 mock 객체를 활용해서 해당 클래스에 의존하는 다른 클래스의 기능을 테스트할 수 있습니다.

     

    이처럼 클래스는 훨씬 더 많은 자유를 보장해 줍니다.

    여기서 더 추상적이게 만들기 위해서는 인터페이스를 사용할 수 있습니다.

     

    4단계 : 인터페이스

     

    코틀린 표준 라이브러리를 읽어보면, 거의 모든 것이 인터페이스로 표현되어 있습니다.

     

    인터페이스를 활용하여 사용자가 추상화된 것에만 의존하게 만들며 결합을 줄일 수 있습니다.

     

    코틀린은 멀티 플랫폼을 지원하며 이넡페이스를 활용하여 안드로이드, IOS, 웹에서 공유해서 사용하는 공통 모듈에 인터페이스를 사용할 수 있습니다.

     

    이제 각각의 플랫폼에서 구현만 조금 다르게 하면 됩니다.

     

    또 다른 장점으로는 모킹 라이브러리를 사용하지 않아도 가짜 인터페이스를 구현해도 됩니다.

     

     

    ID 만들기

    프로젝트에서 고유한 ID를 사용해야 한다면 어떻게 해야 할까요?

     

    가장 간단하게 어떤 정수값을 증가시키면 될 것 같습니다.

    var nextId: Int = 0
    
    val newId = nextId++

     

    이 코드는 ID가 생성되는 방식을 변경할 때 문제가 됩니다.

    만약 미래의 어느 시점 ID를 문자열로 변경해야 한다면 어떨까요?

     

    이제 ID 타입으로 추상화하여 더 많은 자유를 얻을 수 있습니다.

    data calss Id(private val id: Int)
    
    fun getNextId(): Id = Id(nextId++)

     

    추상화가 주는 자유

    - 상수로 추출한다

    - 동작을 함수로 래핑한다

    - 함수를 클래스로 래핑 한다

    - 인터페이스 뒤에 클래스를 숨긴다

    - 보편적인 객체를 특수한 객체로 래핑 한다

     

    하지만 단점으로는 코드를 이해하고 수정하기 어렵게 만듭니다.

     

    추상화의 문제

    추상화는 무한으로 할 수 있지만, 어느 순간부터 득 보다 실이 많아질 수 있습니다.

    이를 풍자한 FizzBuzz Enterprise Edition이라는 프로젝트가 있습니다.

     

    원래 FizzBuzz는 10줄도 필요하지 않은 간단한 예입니다.

    하지만 61개의 클래스와 26개의 인터페이스로 표현합니다.

    이런 코드를 이해하는 것은 굉장히 어렵습니다.

     

    어떻게 균형을 맞추어야 할까?

    극단적인 것은 언제나 좋지 않습니다.

     

    정확한 것은 팀바팀이다..

    - 팀의 크기

    - 팀의 경험

    - 프로젝트의 크기

    - 특징 세트

    - 도메인 지식

     

     

    728x90

    댓글

Designed by Tistory.