Ch8 에서는 드디어 계층형 설계를 위해 코드를 계층에 따라 분리하는 방법에 대해 배울수 있습니다. 그중 직접 구현에 대해 자세히 알 수 있습니다.
Ch8 요약
계층형 설계는 소프트웨어를 계층으로 구성하는 기술이다.
같은 계층에 있는 함수는 같은 목적을 가져야 한다.
직접 구현은 계층형 설계를 하기 위한 방법이다.
직접 구현한 코드는 한 단계의 구체화 수준에 관한 문제만 해결한다.
좋은 설계를 고민하고 만든 코드는 읽거나 고치기 쉽다.
계층형 설계는 특정 구체화 단계에 집중할 수 있게 도와준다.
호출 그래프는 구체화 단계에 대한 풍부한 단서를 보여준다.
함수를 추출하면 더 일반적인 함수로 만들 수 있다.
일반적인 함수가 많을수록 재사용하기 좋다.
함수 이름은 의도를 알려주며 함수 본문은 중요한 세부 사항을 알려준다. 함수 이름을 통해 비슷한 목적을 지닌 함수를 함께 묶을 수 있고 함수 본문을 통해 함수가 어떤 계층 구조에 있어야 하는지 알려준다.
함수를 호출하는 화살표가 다양한 길이를 가지고 있다면 직접 구현되어 있지 않다는 신호이다.
직접 구현된 함수를 읽일 때 함수 시그니처가 나타내고 있는 문제를 함수 본문에서 적절한 구체화 수준에서 해결해야 한다.
인터페이스를 사용하여 세부 구현을 감추고 코드를 작성할 수 있다.
시스템이 커질수록 비즈니스 개념을 나타내는 중요한 인터페이스는 작고 강력한 동작으로 구성하는 것이 좋다.
개발자의 요구를 만족시키면서 비즈니스 문제를 잘 풀 수 있어야 한다.
함수의 이름, 인자명, 리턴값, 구체화 단계, 함수의 본문 등 앞에서 계층화 설계 감각을 키우기 위한 입력에 여러가지 동류가 있었다. 그것을 감안하고 다음 코드를 봐보자.
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;
}
이 함수는 장바구니에 넣은 제품이 tie 혹은 tie clip 이라면 tie clip 을 무료로 주자는 비즈니스 규칙 수준의 함수이다. 이 수준의 함수가 장바구니가 배열이라는 정보는 알 필요가 없다.
이때 책에서는 호출 그래프를 통해 함수 호출에 있어 추상화 단계에 따라 시각화하는 방법에 대해 소개하고 있다.
array index 와 for loop 의 경우 js 자체의 언어 기능이고 make_item, add_item 은 함수를 호출하는 것에 속한다. 높은 단계의 추상화는 더 낮은 단계의 추상화 단계의 코드를 참조하여 동작한다.
이 코드를 개선한다면 같은 추상화 단계의 추상화 계층의 함수를 호출하기 위해 하나의 함수를 추가해줄 수 있다.
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;
}
이렇게 되면 호출 그래프가 다음과 같이 바뀌게 되고 다른 사람들은 함수에서 비슷한 추상화 계층의 함수를 사용하기에 함수를 읽고 파악하기 쉬워진다.
위의 계층형 설계는 3단계의 줌 레벨로 볼 수 있다.
앞선 내용중 직접 구현을 통해 호출 그래프가 같은 추상화 단계의 함수를 호출하도록 했다. 그러나 위의 그림을 보면 함수 줌 레벨에서 볼 때 다른 추상화 단계의 함수를 호출하고 있는 모습을 볼 수 있다. 이는 코드가 정돈되어 있지 않아서이다.
예를 들어 다음의 이름을 기반으로 아이템을 장바구니에서 제거하는 함수의 경우 같은 계층의 함수를 호출하기 위해 새로운 함수 indexOfItem()
함수를 새로 생성하여 사용할 수 있다.
// 원본
function remove_item_by_name(cart, name) {
var idx = null;
for(var i = 0; i < cart.length; i++) {
if(cart[i].name === name)
idx = i;
}
if(idx !== null)
return removeItems(cart, idx, 1);
return cart;
}
// 계층형 설계의 직접 구현 적용시
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;
}
더 아래 계층의 함수를 만들수록 더 일반적인 함수를 만들게 된다.
비슷한 형태의 함수가 있을때 더 일반적인 함수가 무엇인지, 반환값이 다른 함수가 필요한지 여부를 보고 계층형 설계를 적용하여 코드를 개선할 수 있다.
function isInCart(cart, name) {
for(var i = 0; i < cart.length; i++) {
if(cart[i].name === name)
return true;
}
return false;
}
function indexOfItem(cart, name) {
for(var i = 0; i < cart.length; i++) {
if(cart[i].name === name)
return i;
}
return null;
}
두 코드는 유사한 형태를 지녔다. 이때 isInCart
의 반환값은 불린값이라 이 함수를 어떻게 사용하는지 알 필요가 없다. indexOfItem
은 반환값이 index 라 배열 형태의 프로그로밍 언어의 기능이 필요하다.
이 점에서 볼때 isInCart
는 더 높은 추상화 단계에서 index, array 에 대해 알 필요가 없듯이 isInCart
가 더 높은 추상화 단계임을 알 수 있어서 이 함수에서 indexOfItem
을 사용하게 코드를 개선할 수 있다.
function isInCart(cart, name) {
return indexOfItem(cart, name) !== null;
}
function indexOfItem(cart, name) {
for(var i = 0; i < cart.length; i++) {
if(cart[i].name === name)
return i;
}
return null;
}
그러나 항상 이렇게 설계가 좋아지지 않을 수 있다. 오히려 코드가 더 복잡해지고 긴 화살표가 남아있을 경우 더 좋아졌다고 볼 수 없기도 하다.