⭐️ 쏙쏙 들어오는 함수형 코딩 CH9

Yunes·2023년 10월 2일
0
post-thumbnail

바쁜 현대인을 위한 요약

Ch9 에서는 나머지 설계패턴인 추상화 벽, 작은 인터페이스, 편리한 계층에 대해 알 수 있습니다.

Ch9 요약

추상화 벽을 통해 중요한 세부 구현을 감추고 인터페이스를 제공한다.

추상화 벽 위의 함수는 세부 구현을 알 필요가 없다. 만약 새로운 기능을 추가해야 할 때 추상화 벽에 생성한다면 논의해야 할 사항도 많고 세부적인 구현까지 알아야 한다. 그러나 추상화 벽 위에 생성할 경우 굳이 많은 정보를 알 필요 없이 추상화 벽의 인터페이스만 골라 사용할 수 있다. 이렇게 인터페이스를 작게 구성하는 패턴을 작은 인터페이스라고 한다.

추상화를 어느 단계까지 구성해야 하는지는 상황에 따라 다르다. 만약 코드가 동작하는데 문제가 없다면 굳이 더 작게 쪼갤 필요가 없고 로직 파악이 어렵고 코드를 읽기 어렵다면 더 작게 계층을 분리할 필요가 있다는 것이다.

인터페이스를 최소화하면 하위 계층에 불필요한 기능이 쓸데없이 커지는 것을 막을 수 있다.

작은 인터페이스 패턴은 새로운 기능을 만들 때 하위 계층에 기능을 추가하거나 고치는 것보다 상위 계층에 만드는 것을 말한다.

추상화 벽 패턴을 사용시 세부적인 것을 완전히 감출 수 있어 더 높은 차원에서 생각할 수 있다.

호출 그래프 구조에서 규칙을 얻을 수 있다. 이 규칙으로 어떤 코드를 테스트하는 것이 가장 좋은지 유지보수나 재사용하기 좋은 코드는 어디에 있는 코드인지 알 수 있다.

추상화 벽으로 구현을 감출수 있다.

기획자는 구체적인 구현 과정을 알 필요 없이 추상화 벽의 코드를 가져다 사용할 수 있다. 만약 추상화 벽이 잘 구성되어 있다면 데이터 구조를 변경시, 개발자는 기획자가 세부 구현을 알 필요 없으니 변경을 용이하게 할 수 있다.

예를 들어 기존 함수가 배열로 코드가 이루어져 있는데 해시 기반 코드로 바꿔 성능을 향상시키고자 객체 기반 코드로 변경시키려 할 때 추상화 벽의 코드만 수정하면 된다.

/// 배열 기반 장바구니 코드

function add_item(cart, item) {
  return add_element_last(cart, item);
}

function calc_total(cart) {
  var total = 0;
  for(var i = 0; i < cart.length; i++) {
    var item = cart[i];
    total += item.price;
  }
  return total;
}

function setPriceByName(cart, name, price) {
  var cartCopy = cart.slice();
  for(var i = 0; i < cartCopy.length; i++) {
    if(cartCopy[i].name === name)
      cartCopy[i] = setPrice(cartCopy[i], price);
  }
  return cartCopy;
}

function remove_item_by_name(cart, name) {
  var idx = indexOfItem(cart, name);
  if(idx !== null)
    return splice(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;
}

function isInCart(cart, name) {
  return indexOfItem(cart, name) !== null;
}
/// 객체 기반 장바구니 코드

function add_item(cart, item) {
  return objectSet(cart, item.name, item);
}

function calc_total(cart) {
  var total = 0;
  var names = Object.keys(cart);
  for(var i = 0; i < names.length; i++) {
    var item = cart[names[i]];
    total += item.price;
  }
  return total;
}
    
function setPriceByName(cart, name, price) {
  if(isInCart(cart, name)) {
    var item = cart[name];
    var copy = setPrice(item, price);
    return objectSet(cart, name, copy);
  } else {
    var item = make_item(name, price);
    return objectSet(cart, name, item);
  }
}

function remove_item_by_name(cart, name) {
  return objectDelete(cart, name);
}

function isInCart(cart, name) {
  return cart.hasOwnProperty(name);
}

추상화 벽을 사용하기 좋은 경우

  • 쉽게 구현을 바꾸기 위해
    • 추상화 벽 사용시 구현을 간접적으로 사용할 수 있기에 나중에 구현을 바꾸기 쉽다.
  • 코드를 읽고 쓰기 쉽게 만들기 위해
    • 세부적인 것을 신경쓰지 않아도 된다.
  • 팀 간에 조율해야 할 것을 줄이기 위해
  • 주어진 문제에 집중하기 위해
    • 구체적인 사항을 알 필요 없이 특정 문제에 집중할 수 있다.

작은 인터페이스

새 기능을 추가할때 추상화 벽에 만들거나 추상화 벽 위에 만들 수 있다.

// 1. 추상화 벽에 만들때
function getsWatchDiscount(cart) {
  var total = 0;
  var names = Object.keys(cart);
  for(var i = 0; i < names.length; i++) {
    var item = cart[names[i]];
    total += item.price;
  }
  return total > 100 && cart.hasOwnProperty("watch");
}
// 2. 추상화 벽 위에 만들때
function getsWatchDiscount(cart) {
  var total = calcTotal(cart);
  var hasWatch = isInCart("watch");
  return total > 100 && hasWatch;
}

직접 구현 측면에서 비교

추상화 벽에 추가할 경우 완전한 추상화 벽이기에 추상화 벽을 잘 유지할 수 있다. 그러나 마케팅 팀이 구체적인 구현에 신경써야 하기에 직접 구현 측면에서 올바르지 않다.

추상화 벽 측면에서 비교

추상화 벽에 만드는 함수는 개발팀과 마케팅 팀 사이 추가적으로 논의해야 할 사항이 많아진다. 이는 추상화 벽의 장점을 약화시킨다.

작은 인터페이스 측면에서 비교

새 기능을 만들때 하위 계층에 기능을 추가하거나 고치는 것보다 상위 계층에 만드는 것을 작은 인터페이스 패턴이라고 한다. 작은 인터페이스 패턴을 사용하여 하위 계층을 고칠 필요 없이 상위 계층에서 문제를 해결할 수 있다.

장바구니에 제품을 담을 때 로그를 남기는 기능을 추가하고자 하는 상황일때

아이템을 추가할때 로그를 남기면 될까?

function add_item(cart, item) {
  logAddToCart(global_user_id, item);
  return objectSet(cart, item.name, item);
}

여기에 문제가 있다. 로그가 액션이라는 사실때문인데 원래 계산이었던 아이템 추가 함수가 로그로 인해 액션이 되어 액션이 확산되는 결과가 만들어지게 되는 것이다.

그렇게 되면 기존 함수중 무료 배송 아이콘을 추가하는 update_shipping_icons 의 경우에는 장바구니에 제품을 추가하지 않는데도 하위 계층에 로그가 생성되니 로직상 올바르지 않게 동작하게 된다.

function update_shipping_icons(cart) {
  var buttons = get_buy_buttons_dom();
  for(var i = 0; i < buttons.length; i++) {
    var button = buttons[i];
    var item = button.item;
    var new_cart = add_item(cart, item);
    if(gets_free_shipping(new_cart))
      button.show_free_shipping_icon();
    else
      button.hide_free_shipping_icon();
  }
}

그러니 이런 기능의 추가를 추상화 벽 위에 있는 계층에서 호출하도록 코드를 작은 인터페이스를 유지하게 작성해 볼 수 있다.

function add_item_to_cart(name, price) {
  var item = make_cart_item(name, price);
  shopping_cart = add_item(shopping_cart, item);
  var total = calc_total(shopping_cart);
  set_cart_total_dom(total);
  update_shipping_icons(shopping_cart);
  update_tax_dom(total);
  logAddToCart();
}

추상화 벽을 작게 만들어야 하는 이유

  • 추상화 벽에 코드가 많을수록 구현이 변경될 때 고쳐야 할 것이 많다.
  • 추상화 벽에 있는 코드는 낮은 수준의 코드이기에 더 많은 버그가 있을 수 있다.
  • 낮은 수준의 코드는 이해하기 더 어렵다.
  • 추상화 벽에 코드가 많을수록 팀 간 조율해야 할 것이 많아진다.
  • 추상화 벽에 인터페이스가 많으면 알아야 할 것이 많아 사용하기 어렵다.

편리한 계층

  • 작업하는 코드가 편리하다고 느낀다면 설계를 멈춰도 된다.
  • 구체적인 것을 너무 많이 알아야 하거나 코드가 지저분하다고 느껴지면 다시 패턴을 적용하자.

호출 그래프로 알 수 있는 코드에 대한 정보

기능적 요구사항 - 소프트웨어가 정확히 해야 하는 일

비기능적 요구사항 - 테스트를 어떻게 해야 할 것인지, 재사용을 잘 할 수 있느지, 유지보수하기 어렵지 않은지와 같은 요구사항

유지 보수성 maintainability

요구사항이 바뀌었을 때 가장 쉽게 고칠수 있는 코드는?

  • 가장 위에 있는 코드는 어디서도 호출하지 않으니 고치기 쉽다. 그러나 바뀌는 것이 많은 가장 높은 곳은 적게 유지하는 것이 좋다.

테스트성 testability

어떤 것을 테스트하는 것이 가장 중요한가?

  • 위에 있는 코드를 테스트시 많은 코드를 확인할 수 있다. 다만 자주 바뀌기에 테스트가 오래 유효하지 않다.
  • 가장 아래의 코드를 테스트시 많은 코드가 해당 코드에 의존하기에 유용하다.

하위 계층 코드를 테스트할수록 얻는 것이 더 오래간다.

재사용성 reusability

어떤 함수가 재사용하기 좋은가?

아래에 있는 코드는 의존하는 코드도 많고 쉽게 변하지 않으니 재사용하기 용이하다. 아래쪽으로 가리키는 화살표가 많은 함수는 재사용하기 어렵다.

분류유지 보수성테스트 가능성재사용성
규칙위로 연결된 것이 적은 함수가 바꾸기 쉽다.위쪽으로 많이 연결된 함수를 테스트하는 것이 더 가치있다.아래쪽에 함수가 적을수록 더 재사용하기 좋다.
핵심자주 바뀌는 코드는 가능한 위쪽에 있어야 한다.아래쪽에 있는 함수를 테스트하는 것이 위쪽에 있는 함수를 테스트하는 것보다 가치 있다.낮은 수준의 단계로 함수를 빼내면 재사용성이 더 높아진다.
profile
미래의 나를 만들어나가는 한 개발자의 블로그입니다.

0개의 댓글