이 책에서는 소프트웨어 설계를 코드를 만들고, 테스트하고, 유지보수하기 쉬운 프로그래밍 방법을 선택하기 위해 미적 감각을 사용하는 것이라고 말한다. 소프트웨어 설계를 위한 미적 감각은 계층형 설계를 통해 키울 수 있을 것이라 기대한다.
여기서 계층형 설계는 소프트웨어를 계층으로 구성하는 기술을 말한다.
직접 구현된 함수를 읽을 때, 함수 시그니처가 나타내고 있는 문제를 함수 본문에서 적절한 구체화 수준에서 해결해야 한다.
계층형 설계를 할 때 어떤 계층에서는 세부 구현을 감추고 인터페이스를 제공한다.
비즈니스 개념을 나타내는 중요한 인터페이스는 작고 강력한 동작으로 구성하는 것이 좋다.
계층을 잘 나누고 각 계층이 하는 일을 잘 추상화 해야 한다.
아래는 넥타이 하나를 사면 무료로 넥타이 클립을 하나 주는 코드이다.
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;
}
한 함수에서 아주 많은 일들을 하고 있다. 장바구니를 돌고, 항목을 체크하고, 또 무언가를 결정하고...
이 코드는 첫번째 계층형 설계 패턴인 직접 구현을 따르지 않고 있다. 게다가 마케팅과 관련된 함수인데 배열인 장바구니를 순회하는 등 너무 구체적인 내용을 담고 있다.
function add_item(cart, item) {
return add_element_last(cart, item);
}
function remove_item_by_name(cart, name) {
var idx = null;
for(let i = 0; i < cart.length; i++) {
if(cart[i].name === name)
idx = i;
}
if(idx !== null)
return removeItems(cart, idx ,1);
return cart;
}
장바구니에 제품이 있는지 확인하기
아직 구현하지 않음
합계 계산하기
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 cartTax(cart) {
return calc_tax(calc_total(cart));
}
function gets_free_shipping(cart) {
return calc_total(cart) >= 20;
}
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()
함수를 보니 직접 구현 패턴을 적용할 수 있을 것 같다.
장바구니 안에 제품이 있는지 확인하는 함수가 있다면 계층의 저수준인 for문을 사용하지 않아도 된다.
function isInCart(cart, name) {
for(var i = 0; i < cart.length; i++) {
if(cart[i].name === name)
return true;
}
return false;
}
위처럼 함수에서의 for문 빼내어 함수를 만들고 아래처럼 직접 구현 패턴을 적용할 수 있다.
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;
}
지금까지 작성한 함수들을 계층을 나누어 생각해볼 수 있다.
함수 그래프를 그려 함수 호출을 시각화해보면 아래와 같다.
같은 계층에 있는 함수는 같은 목적을 가졌고, 함수가 호출하는 함수를 화살표로 가리켰다.
위 그래프는 그렇지 않지만, 직접 구현 패턴을 사용하면 모든 화살표가 같은 길이를 가져야 한다.
이 그래프처럼 화살표가 다양한 계층을 넘나드는 것은 어떤 것은 너무 많이 구체화 했고, 어떤 것은 덜 구체화했다는 증거이다.
직접 구현 패턴이 지향하는 것은 상위 계층에 있는 함수가 바로 아래 계층의 동작에 의존하게 만드는 것이다. 쉽게 말하면 모두 같은 길이의 화살표를 가져야 한다.
🤷♀️ 어떻게 같은 길이의 화살표를 가지게 만드나요?
일반적인 방법으로는 함수에서 코드를 추출하여 추출한 코드를 가진 함수를 추가하여 화살표 길이를 짧게 만드는 것이다.
이렇게 화살표 길이를 줄이는 것에 집중하면 더 좋은 계층 구조를 만들 수 있다.
화살표 길이를 줄이다보면 오히려 구조가 더 복잡해지는 경우도 있지만, 일단은 화살표 길이를 줄이는 것에 집중해야 한다. 계층형 설계에서 모든 계층은 바로 아래 계층에 의존해야 한다.
직접 구현한 코드는 한 단계의 구체화 수준에 관한 문제만 해결한다.
계층형 설계는 특정 구체화 단계에 집중할 수 있게 도와준다.
함수를 추출하면 더 일반적인 함수로 만들 수 있다.
혹시나 잘못된 정보가 있다면 댓글로 알려주세요 ! 저의 성장의 큰 도움이 될 것 같습니다.🌱