본문으로 건너뛰기

2. 추상화 계층

왜 추상화 계층을 만드는가?​

코드 작성은 복잡한 문제를 계속해서 더 작은 하위 문제로 세분화하는 작업입니다.

어떤 문제를 하위 문제로 계속해서 나누어 내려가면서 추상화 계층을 만든다면, 같은 층위 내에서는 쉽게 이해할 수 있는 몇 개의 개념만을 다루기 때문에 개별 코드는 특별히 복잡해 보이지 않을 것입니다.

비록 문제가 엄청나게 복잡할지라도 하위 문제들을 식별하고 올바른 추상화 계층으로 만듦으로써 그 복잡한 문제를 쉽게 다룰 수 있습니다.

추상화 계층 및 코드 품질의 핵심 요소​

  • 가독성
    • 개발자들이 코드베이스에 있는 코드의 모든 세부 사항을 이해하는 것은 불가능합니다.
    • 하지만 몇 가지 높은 계층의 추상화를 이해하고 사용하기는 상당히 쉽습니다.
    • 깨끗하고 뚜렷한 추상화 계층을 만드는 것은 개발자가 한 번에 한두 개 정도의 계층과 몇 개의 개념만 다루면 된다는 것을 의미합니다.
  • 모듈화
    • 추상화 계층이 하위 문제에 대한 해결책을 깔끔하게 나누고 구현 세부 사항이 외부로 노출되지 않도록 보장할 때, 다른 계층이나 코드의 일부에 영향을 미치지 않고 계층 내에서만 구현을 변경하기가 쉬워집니다.
  • 재사용성 및 일반화성
    • 하위 문제에 대한 해결책이 간결한 추상화 계층으로 제시되면 해당 하위 문제에 대한 해결책을 재사용하기가 쉬워집니다.
  • 테스트 용이성
    • 코드가 추상화 계층으로 깨끗하게 분할되면 각 하위 문제에 대한 해결책을 완벽하게 테스트하는 것이 훨씬 쉬워집니다.

코드의 계층​

실제로 추상화 계층을 생성하는 방법은 코드를 서로 다른 단위로 분할하여 단위 간의 의존 관계를 보여주는 의존성 그래프를 생성하는 것이다.

api 및 구현 세부사항​

API(application programming interface)는 서비스를 사용할 때 알아야 할 것들에 대한 개념을 형식화하고, 서비스의 구현 세부 사항은 API 뒤로 감춘다.

API는 호출하는 쪽에 공개할 개념만 정의하면 되고 그 이외의 모든 것은 구현 세부 사항이기 때문에 코드를 API의 관점에서 생각하면 추상화 계층을 명확하게 만드는 데 도움이 된다.

코드의 일부를 작성하거나 수정할 때, API에 이 수정 사항에 대한 구현 세부 정보가 새어 나간다면 추상화 계층이 명확하게 구분되어 이루어진 것이 아니다.

함수​

함수를 작게 만들고 수행하는 작업을 명확하게 하면 코드의 가독성과 재사용성이 높아진다.

하나의 고수준의 문제를 해결하기 위해 단 하나의 큰 함수를 만들기 보다 하위문제로 나눠서 여러개의 작은 함수를 만들고 조합하는게 낫다.

클래스​

단일 클래스 내에 얼마나 많은 다른 개념이 들어가 있는지, 어떤 로직이 재사용이나 재구성에 적합한지에 대해 개발자가 신중하게 생각하지 않으면 클래스는 종종 너무 커진다.

단일 클래스가 커지면 생기는 문제점은 다음과 같다.

  • 코드 가독성
    • 단일 클래스에 담겨있는 개념이 많을수록 해당 클래스의 가독성은 저하된다.
  • 코드 모듈화
    • 하위 문제에 대한 해결책이 하나의 클래스로 구현되어 있고, 다른 클래스와의 상호작용은 잘 준비된 몇가지 퍼블릭 함수를 통해서만 이루어진다면, 그 하위 문제에 대한 해결책의 구현을 다른 클래스로 교체하기 쉽다. (단일 클래스는 어려움)
  • 코드 재사용성 및 일반화
    • 어떤 문제를 해결할 때 두 가지 하위 문제를 해결해야 하는 경우, 나중에 다른 누군가가 그 하위 문제 중 하나를 해결해야 하는 상황이 올 가능성이 크다.
    • 두 하위 문제에 대한 해결책을 한 클래스로 묶어 놓으면 다른 누군가가 이미 구현된 한 가지 해결책을 재사용할 기회가 줄어든다.
  • 테스트 용이성 및 적절한 테스트

클래스를 나누고 의존성 주입을 통해 클래스간의 상호작용으로 구현하자.

인터페이스​

계층 사이를 뚜렷이 구분하고 구현 세부 사항이 계층 사이에 유출되지 않도록 하기 위해 사용할 수 있는 한 가지 접근법은 어떤 함수를 외부로 노출한 것인지를 인터페이스를 통해 결정하는 것이다.

인터페이스에 정의된 대로 클래스가 해당 계층에 대한 코드를 구현한다. 이보다 위에 있는 계층은 인터페이스에 의존할 뿐 로직을 구현하는 구체적인 클래스에 의존하지 않는다.

층이 너무 얇아질 때​

코드를 별개의 계층으로 세분화하면 장점이 많지만 다음과 같은 추가 비용이 발생한다.

  • 클래스를 정의하고 임포트하는 반복적인 코드로 인해 코드의 양을 늘린다.
  • 로직의 이해를 위해 파일이나 클래스를 따라갈 때 많은 노력이 필요
  • 인터페이스 뒤에 계층을 숨기게 되면 어떤 상황에서 어떤 구현이 사용되는지 파악하는데 더 많은 노력이 필요하다. 이로 인해 디버깅이 어려워질 수 있다.

계층의 역할을 적당히 유지하는게 필요하다.

하지만 일반적으로 너무 많은 일을 하는 계층은 너무 적은 일을 하는 계층보다 더 문제가 된다. 따라서 어떤 것이 더 나을지 확실하지 않다면 너무 많은 계층을 남용하더라도 계층을 여러 개로 나누는 것이 한 계층 안에 모든 코드를 집어 넣는 것보다는 낫다. 나중에 고치자.