본문 바로가기
오늘은 뭘 배울까?/책

[이펙티브 코틀린] 4장. 추상화 설계

by Kim Juhwan 2022. 11. 25.

1. 함수 내부의 추상화 레벨을 통일하라
2. 변화로부터 코드를 보호하려면 추상화를 사용하라
3. API 안정성을 확인하라
4. 외부 API를 랩(wrap)해서 사용하라

5. 요소의 가시성을 최소화하라
6. 문서로 규약을 정의하라
7. 추상화 규약을 지켜라

 

 

 


 

 

1. 추상화란? (153p ~ )

구글에 추상화를 검색하면 나오는 그림들

 

추상화를 검색하면 위와 같이 뜬다.

미술에서의 추상화는 구체적인 형상을 그린 것이 아니라

대상의 특징을 뽑아 점, 선, 색 등과 같은 것으로 표현한 것을 의미한다.

 

즉, 불필요한 것은 그리지 않고, 최소한의 요소를 사용해 그리는 것이 추상화이다. 

 

설계도

 

건축에서의 추상화도 비슷한 의미를 가진다.

집을 구체적으로 그려 설계를 하는 것이 아니라

필요한 부분만 특징을 쏙쏙 뽑아 그리는 것이 설계도이다.

 

프로그래밍에서의 추상화도 같다.

대상의 속성을 다 구현해내는 것이 아니라 필요한 것들만을 구현해낸다.

구현하는 과정에서 불필요한 것은 감추고, 필요한 것은 노출한다.

 

예를 들어 우리가 숫자를 입력하면 컴퓨터 내부에서는 0과 1이라는 복잡한 식으로 이를 표현한다.

이러한 것들이 추상화되어 있기 때문에 우리는 01010101110... 같은 복잡한 식을 편하게 사용할 수 있는 것이다.

 

2. 함수 내부의 추상화 레벨을 통일하라 (158p ~ )

애플리케이션
프로그래밍 언어
어셈블러
하드웨어
물리 장치

위 표는 추상화 계층을 나타낸 것이다. (위로 갈수록 높은 레벨)

우리가 하드웨어를 제어하는 방법을 몰라도 되는 이유는 어셈블러가 있기 때문이고

어셈블러를 몰라도 되는 이유는 프로그래밍 언어가 있기 때문이다.

이렇게 계층이 잘 분리되어 있다면 전체를 이해할 필요 없이

해당 계층만 생각하고 작업하면 된다.

 

class CoffeeMachine {
    fun makeCoffee() {

    }
}

이 개념은 함수에도 적용이 된다.

makeCoffee에 1000줄 짜리 로직이 들어간다 치자.

어느 날 "물의 온도를 변경해주세요"라는 요청을 받는다면

우리는 이 1000줄 짜리 로직을 다 읽어봐야 할지도 모른다.

 

class CoffeeMachine {
    fun makeCoffee() {
    	boilWater()
        brewCoffee()
        pourCoffee()
        pourMilk()
    }
    
    ...
}

하지만 이렇게 함수를 나눠서 작성하면 우리는 앞선 요청을 받았을 때 boilWater만 읽어보면 될 것이다.

계층이 생김으로써 우리는 boilWater만 신경쓰고 나머지 계층은 신경 쓰지 않아도 되는 것이다!

 

boilWater
brewCoffee
pourCoffee
pourMilk

아까처럼 계층구조로 보자면 이런 느낌..!

 

예시를 함수로 들었지만 추상화 계층이라는 개념은 더 크게 바라볼 수도 있다.

예를 들어 프론트가 높은 레벨이라면 백엔드는 낮은 레벨이라고 볼 수 있다.

 

3. 변화로부터 코드를 보호하려면 추상화를 이용하라 (164p ~ )

  • 상수로 추출한다.
  • 동작을 함수로 래핑한다.
  • 함수를 클래스로 래핑한다.
  • 인터페이스 뒤에 클래스를 숨긴다.
  • 보편적인 객체를 특수한 객체로 래핑한다.

추상화를 하는 방법에는 여러 가지가 있다. 책에서 몇 가지 예시를 들어주고 있어 주욱 읽어봄직하다.

중요한 건 그다음인 것 같다.

그렇다면 추상화는 항상 좋은 것인가?

그렇지 않다.

 

 

GitHub - EnterpriseQualityCoding/FizzBuzzEnterpriseEdition: FizzBuzz Enterprise Edition is a no-nonsense implementation of FizzB

FizzBuzz Enterprise Edition is a no-nonsense implementation of FizzBuzz made by serious businessmen for serious business purposes. - GitHub - EnterpriseQualityCoding/FizzBuzzEnterpriseEdition: Fizz...

github.com

추상화는 거의 무한하게 할 수 있지만 어느 순간부터는 득보다 실이 많다고 한다.

이를 풍자한 프로젝트가 있는데, 이 프로젝트에서는 10줄도 필요하지 않은 코드에 추상화를 무진장 접목시켜

61개의 클래스와 26개의 인터페이스가 있다고 한다.

이걸 만든 사람도 정말 제정신이 아니라는 생각이 든다. 흥미로우니 한 번 들어가 보길...

 

그렇다면 추상화를 어느 정도까지 적용해야 할까?

저자가 몇 가지 간단한 조언을 주고 있지만 결국 오랜 경험으로 나오는 감이 필요하다고 말하고 있다.

 

4. API 안정성을 확인하라 (178p ~ )

이 챕터에서 가장 흥미로운 부분은 버전 네이밍에 대한 이야기였다.

예를 들어 앱 버전을 1.2.0 이런 식으로 표기하곤 하는데 각각의 위치가 의미하는 바는 다음과 같다.

 

[Major]_[Minor]_[Patch]

  • Major: 호환되지 않는 수준의 API 변경
  • Minor: 이전 변경과 호환되는 기능을 추가
  • Patch: 간단한 버그 수정

 

여기까지는 원래 알던 내용이었는데 Major가 0번대이면 아직 안정화 버전이 아니라는 것을 여태 몰랐다.

그래서 라이브러리 같은 거 사용할 때 0.x.x 버전을 사용하면 안정적일 거라고 생각하면 안 된다고 한다.

그리고 또 흥미로운 건 코틀린이 1 버전대로 올라오는 데 5년이 걸렸다고 한다. 와우 ...

 

또, API의 작은 변경이 이를 활용하는 다른 코드들의 많은 부분을 변경하게 만들 수 있다고 말하고 있다.

가령 버전 업데이트의 경우에도 이런 일이 발생할 수 있기 때문에 이전 라이브러리 버전을 유지하는 경우가 많다고 한다.

하지만 그렇게 되면 버그와 취약성이 발견될 수 있으니

개발자라면...! 업데이트를 두려워하지 말라고 한다.

 

5. 외부 API를 랩(wrap)해서 사용하라 (182p ~ )

불안정한 API를 로직과 직접 결합하는 것은 위험하다.

그래서 외부 API를 사용할 때는 랩하여 사용하라고 한다.

음. 간단한 예시로 glide라는 유명한 라이브러리를 들어보자. (물론 안전한 라이브러리지만. 예시를 위해)

이미지 로딩을 하는 glide를 프로젝트 여기저기서 사용하고 있다고 해보자.

어느 날 glide에 큰 결함이 생겨 다른 라이브러리를 사용해야 한다면 glide가 사용되는 모든 곳을 찾아 다 변경해야 할 것이다.

하지만 만약 함수나 클래스 등으로 감싸서 사용하면 해당 래퍼만 변경하면 되므로 API 변경에 쉽게 대응할 수 있을 것이다.

 

그 외에도 다음과 같은 장점이 있다.

  • 프로젝트 스타일에 맞춰서 API의 형태를 조정할 수 있다.
  • 특정 라이브러리에서 문제가 발생하면, 래퍼를 수정해서 다른 라이브러리를 사용하도록 코드를 쉽게 변경할 수 있다.
  • 필요한 경우 쉽게 동작을 추가하거나 수정할 수 있다.

 

반면 단점은 다음과 같다.

  • 래퍼를 따로 정의해야 한다.
  • 다른 개발자가 프로젝트를 다룰 때, 어떤 래퍼들이 있는지 따로 확인해야 한다.
  • 래퍼들은 프로젝트 내부에서만 유효하므로, 문제가 생겨도 질문할 수 없다.

 

현재 사용하려는 라이브러리의 안정성과 상황을 고려하여 래퍼를 사용할지 말지 결정하면 된다.

 

6. 문서로 규약을 정의하라 (189p ~ )

KDoc이라는 게 있길래 적어보려고 한다.

k-pop, k-드라마에 이어 이제는 k-doc이라니...

는 넝담이고 KDoc은 Kotlin Document라는 뜻이다..ㅎㅎ

 

    /**
    * Universal way for the project to display a short
    * message to a user
    * @param message The text that should be shown to
    * the user
    * @param length How long to display the message.
    */
    fun ... {
    
    }

위처럼 주석으로 함수를 문서화할 때 사용되는 공식적인 형식을 KDoc이라고 부른다.

함수 내부 코드를 까 본 경험이 있다면 본 적이 있을 것이다.

 

내가 라이브러리를 만들 날은 아직 멀은 것 같고

확장 함수를 만들 때 훗날 함수를 사용할 팀원들을 위해 앞으로 KDoc에 맞춰 주석을 작성해야겠다는 생각이 들었다.

 

그러면서 책에 주석에 대한 언급을 잠깐 하는데 꽤 흥미롭다.

예전에는 문학적 프로그래밍이라고 해서 주석으로 모든 것을 설명하는 프로그래밍 방식이 인기였다고 한다.

하지만 10년이 지나고 주석 없이도 읽을 수 있는 코드를 작성해야 한다는 프로그래밍 방식으로 바뀌었고

이런 변화를 일으킨 데 가장 큰 영향을 준 책이 바로 클린 코드라고 한다.

세상에! 그 책이 그 책이었다니!

이 사람 책 중 클린 아키텍처라는 책을 가지고 있는데 이거랑 클린 코드도 나중에 꼭 읽어봐야겠다.

 

아.무.튼

저자는 오케이. 주석 없이도 읽을 수 있는 코드? 좋다.

하지만 주석을 함께 사용하면 더 많은 내용을 설명할 수 있다고 말하고 있다.

 

fun update() {
    // 사용자를 업데이트합니다.
    for (user in users) {
    	user.update()
    }
    
    // 책을 업데이트합니다.
    for (book in books) {
    	updateBook(book)
    }
}

주석이 필요 없는 경우를 잘 나타내는 예시이다.

내용이 간단해서 필요 없다거나 한 것이 아니다. 아래 코드를 보자.

 

fun update() {
    updateUsers()
    updateBooks()
}

private fun updateBooks() {
    for (book in books) {
    	updateBook(book)
    }
}

private fun updateUsers() {
    for (user in users) {
    	user.update()
    }
}

이렇게 함수를 나누면 주석 없이도 충분히 읽을 수 있는 코드가 된다

주석 오용, 남용되면 안 좋은 거지 결코 나쁜 것이 아니다.

상황에 맞게 적절히 사용하자.

 

 


💡 느낀 점

  • 책 내용 중 "물 위를 걷는 것과 명세서로 소프트웨어를 개발하는 것은 쉽다. 둘 다 동결되어 있다면..."이란 문장이 너무 웃겼다.
    외국 아재개그 재밌넹...
  • 처음으로 모듈과 인터페이스를 이용해 추상화 계층을 나눌 때 도대체 이 복잡한 짓을 왜 하나 싶었던 기억이 난다.
    구조 짜는데 시간도 더 오래 걸리는 것 같고... 그 의문에 답을 얻을 수 있는 목차이지 않았나 싶다.
  • 예전에 다른 책을 읽고 느낀 점이 있다.
    좋은 코드 > 나쁜 코드 + 좋은 주석이지만
    좋은 코드 < 좋은 코드 + 좋은 주석이라는 점.
    이번 저자도 같은 의견을 제시하고 있어서 괜스레 기분이 좋았다.

📘 참고한 자료

  • Effective Kotlin - 마르친 모스칼라 지음, 윤인성 옮김

 

 

반응형

댓글