Skip to content

계층형 설계 I

코드가 복잡해지는 가장 흔한 이유는 서로 다른 추상화 수준의 로직이 한 함수 안에 섞여 있기 때문입니다.
이럴 때 해결책이 되는 것이 바로 계층형 설계(Stratified Design) 입니다.

계층형 설계는 코드를 여러 계층으로 나누고, 각 계층이 바로 아래 계층에만 의존하도록 만드는 방식입니다.
이번 글에서는 그중 첫 번째 패턴인 직접 구현(Straightforward Implementation) 에 대해 다룹니다.

스터디 회차: 7회차 (2025년 8월 26일)

계층형 설계 (Stratified Design)

계층형 설계란

  • 계층형 설계는 소프트웨어를 여러 계층으로 나누고, 각 계층이 바로 아래 계층에만 의존하도록 구성하는 방법입니다.
  • 이를 잘하기 위해서는 함수 본문, 계층 구조, 함수 시그니처 등 다양한 요소를 고려하고, 새로운 함수를 어느 위치에 둘지, 어떻게 구현할지를 끊임없이 결정해야 합니다.

계층형 설계 패턴

  • 직접 구현: 문제를 적절한 추상화 수준에서 해결하기
  • 추상화 벽: 세부 구현을 감추고 인터페이스 제공하기
  • 작은 인터페이스: 꼭 필요한 최소한의 인터페이스 유지하기
  • 편리한 계층: 작업하기에 편리한 추상화 계층 제공하기

직접 구현 패턴 (Straightforward implementation)

직접 구현 패턴이란

  • 코드를 설계할 때는 가능한 한 단순하게 구현하는 것이 좋습니다.
  • 특정 기능을 작성할 때는 그 기능을 해당 계층에서 직접 해결해야 하며, 불필요하게 다른 추상화 단계로 넘기거나 감출 필요가 없습니다.
  • 또한 기능을 작은 단위의 함수로 나누어 명확하고 단순한 구조를 유지하는 것이 바람직합니다.

즉, 직접 구현 패턴은 문제를 우회하거나 숨기지 않고 현재 계층에서 곧바로 해결하는 방식을 의미합니다.

예제: 넥타이를 사면 넥타이 클립 무료 제공

tsx
function freeTieClip(cart) {
  var hasTie = false;
  var hasTieClip = false;
  for (var i = 0; i < cart.length; i++) {
    var item = cart[i];
    if (item.name === "tie") hasTie = true;
    if (item.name === "tie clip") hasTieClip = true;
  }
  if (hasTie && !hasTieClip) {
    var tieClip = make_item("tie clip", 0);
    return add_item(cart, tieClip);
  }
  return cart;
}

문제점

  • freeTieClip 함수가 장바구니 내부 구조(배열, 인덱스 탐색 등)를 직접 다룹니다.
  • 그 결과, 비즈니스 로직과 세부 구현이 섞여 가독성이 떨어집니다.

개선하기

tsx
function freeTieClip(cart) {
  var hasTie = isInCart(cart, "tie");
  var hasTieClip = isInCart(cart, "tie clip");
  if (hasTie && !hasTieClip) {
    var tieClip = make_item("tie clip", 0);
    return add_item(cart, tieClip);
  }
  return cart;
}

function isInCart(cart, name) {
  for (var i = 0; i < cart.length; i++) {
    if (cart[i].name === name) return true;
  }
  return false;
}
  • 세부구현 부분을 isInCart 함수로 분리하여 추상화 수준을 비슷하게 맞춥니다.
  • freeTieClip 안에 있는 isInCart, make_item, add_item 들이 비슷한 추상화를 가집니다.

호출 그래프로 알아보기

계층형 설계

개선 전

  • freeTieClip반복문, 배열 인덱스 같은 저수준 동작과 비즈니스 로직을 동시에 다룹니다.
  • 한 함수가 서로 다른 추상화 단계를 섞어 사용하므로 가독성이 떨어집니다.

개선 후

  • 모두 비슷한 추상화 수준의 함수들이어서 호출 구조가 단순해졌습니다.
  • freeTieClip은 이제 장바구니가 배열인지조차 알 필요가 없어졌습니다.

TIP

계층형 설계는 정답이 있는 기법이 아닙니다.
여러 관점에서 시도하고 경험적으로 다듬어가는 과정입니다.

계층 결정하기

예제: remove_item_by_name을 계층 구조에 추가하기

계층형 설계

계층형 설계

  • freeTieClip은 마케팅 캠페인에 대한 로직이고, remove_item_by_name는 마케팅과 관련없는 일반적인 동작이므로 freeTieClip보다 낮은 계층에 위치합니다.
  • isInCartremove_item_by_name는 같은 박스를 가리키고 있습니다.
    • 같은 박스를 가리킨다는 것은 동일한 계층에서 다뤄져야 한다는 신호입니다.
  • 따라서 remove_item_by_nameisInCart와 동일한 계층에 두는 것이 적절합니다.

TIP

함수 이름은 함수가 어느 곳에 위치할지 결정하기 위한 정보로 쓸 수 있습니다.

더 좋은 계층 구조 만들기

같은 계층에 있는 함수는 같은 목적(같은 구체화 수준)을 가져야 합니다.

계층형 설계

개선 전

  • 직접 구현 패턴을 사용하면 모든 화살표가 같은 길이를 가져야합니다.
  • 다양한 계층을 넘나드는 것을 통해 같은 구체화 수준이 아니라는 것을 알 수 있습니다.

개선 후

  • 가장 일반적인 방법은 중간에 함수를 두는 것입니다.
tsx
function remove_item_by_name(cart, name) {
  var idx = indexOfItem(cart, name);
  if (idx !== null) return removeItems(cart, idx, 1);
  return cart;
}

function indexOfItem(cart, name) {
  for (var i = 0; i < cart.length; i++) {
    if (cart[i].name === name) return i;
  }
  return null;
}
  • indexOfItem를 추가하여 배열의 세부 구현은 indexOfItem에서 다루고, remove_item_by_name 함수 내에서 비슷한 계층의 함수를 호출하도록 개선합니다.

정확히는 indexOfItem 함수가 removeItems 함수보다 조금 더 위에 위치합니다

ts
function indexOfItem(cart, name) {
  for (var i = 0; i < cart.length; i++) {
    if (cart[i].name === name) return i;
  }
  return null;
}

function removeItems(array, idx, count) {
  var copy = array.slice();
  copy.splice(idx, count);
  return copy;
}
  • indexOfItem 함수는 배열에 항목이 name 속성이 있다는 것을 알아야하지만 removeItems 함수는 배열에 들어있는 항목이 어떻게 생겼는지 몰라도 됩니다.
  • 그래서 removeItems 함수는 indexOfItem 함수보다 더 일반적이고 indexOfItem 함수보다 조금 더 낮은 계층에 있습니다.

정리하기

  • 한 함수 안에 서로 다른 추상화 수준이 섞이면 읽기 어렵습니다.
  • 직접 구현 패턴은 문제를 우회하거나 숨기지 않고 현재 계층에서 곧바로 해결하는 방식을 의미합니다.
  • 개선 방법 중 하나는, 구체적인 로직을 별도의 일반화된 함수로 분리하는 것입니다.