본문으로 건너뛰기

2. 추상화 계층

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

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

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

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

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

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

코드의 계층

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

api 및 구현 세부사항

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

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

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

함수

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

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

클래스

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

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

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

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

인터페이스

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

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

층이 너무 얇아질 때

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

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

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

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