계층형 설계 I
- 8.1 계층형 설계란
- 8.2 직접 구현 패턴
- 8.3 호출 그래프
- 8.4 줌 레벨
- 8.5 같은 계층에 있는 함수들
함수 본문을 읽었을 때 너무 구체적인 구현 세부사항이 보이면, 그건 추상화가 부족하다는 신호야.
8장부터는 시야를 넓혀서 함수들의 구조를 봐. 개별 함수가 아니라 함수들이 어떻게 조직되어야 하는지, **계층형 설계(stratified design)**의 첫 번째 패턴을 다루거든. 계층형 설계는 함수들을 추상화 수준에 따라 계층으로 나누는 설계 방법이야. 가장 아래층에는 프로그래밍 언어가 제공하는 기본 기능(배열 인덱싱, 객체 속성 접근 등)이 있고, 그 위에 유틸리티 함수, 그 위에 비즈니스 로직, 그 위에 UI 로직 같은 식으로 쌓아 올려. 각 계층의 함수는 자기보다 한 단계 아래 계층의 함수만 호출하는 게 이상적이야. 이렇게 하면 각 계층이 독립적으로 이해 가능하고, 아래 계층을 바꿔도 위 계층에 영향이 작고, 테스트도 계층별로 할 수 있지. MegaMart 예제로 보면 최하위는 자바스크립트 배열/객체 연산, 중간은 add_item(), remove_item(), set_price() 같은 장바구니 유틸리티, 상위는 add_item_to_cart(), update_shipping() 같은 비즈니스 로직, 최상위는 DOM 업데이트, 이벤트 핸들러야.
계층형 설계의 첫 번째 패턴이 **직접 구현(straightforward implementation)**인데, 함수가 자신이 해야 할 일을 같은 추상화 수준의 연산들로 표현하는 거야. 추상화 수준이 섞이면 안 돼.
// 나쁜 예
function gets_free_shipping(cart) {
var total = 0;
for (var i = 0; i < cart.length; i++) { // 너무 낮은 수준
total += cart[i].price; // 배열 인덱싱, 속성 접근
}
return total >= 20;
}
// 좋은 예
function gets_free_shipping(cart) {
return calc_total(cart) >= 20;
}
나쁜 예는 "무료 배송 여부 판단"이라는 비즈니스 로직인데, 안에서 배열 반복이나 인덱싱 같은 낮은 수준의 구현 세부사항이 보여. 좋은 예는 calc_total()이라는 같은 수준의 함수를 호출해서 명확하게 표현하지. 이 함수를 읽으면 "합계가 20 이상이면 무료 배송"이라는 비즈니스 규칙이 바로 보여.
함수들 사이의 호출 관계를 시각화한 것이 **호출 그래프(call graph)**야.
add_item_to_cart()
├── add_item()
│ ├── array.slice()
│ └── array.push()
├── calc_total()
│ └── array.reduce()
└── update_dom()
호출 그래프를 그리면 어떤 함수가 어떤 계층에 있는지 눈에 보이고, 한 함수가 여러 계층의 함수를 건너뛰며 호출하고 있으면 설계 문제를 발견할 수 있고, 순환 참조도 바로 발견할 수 있어. 저자는 이 호출 그래프를 위에서 아래로 읽을 수 있어야 한다고 말해. 위쪽은 높은 추상화, 아래쪽은 낮은 추상화. 화살표가 항상 아래를 향해야 깔끔한 설계거든.
호출 그래프를 다양한 **줌 레벨(zoom level)**로 볼 수 있다는 개념도 재밌어. 줌 아웃하면 전체 시스템의 큰 구조를 보고("장바구니 모듈과 결제 모듈은 어떻게 연결되어 있나"), 줌 인하면 특정 함수 하나를 깊이 봐("이 함수가 호출하는 것들의 추상화 수준이 일관된가"). 같은 호출 그래프를 줌 레벨을 바꿔가며 보면 다른 설계 문제가 보이거든. 줌 아웃하면 모듈 간 결합도가 보이고, 줌 인하면 함수 내부의 추상화 수준 불일치가 보여.
같은 계층에 있는 함수들은 비슷한 추상화 수준을 가져야 해. 예를 들어 장바구니 유틸리티 계층에는 add_item(cart, item), remove_item(cart, name), set_quantity(cart, name, quantity) 같은 함수들이 있어야 하고, 여기에 send_email(user, cart)이 들어오면 이상하지 — 이메일 전송은 더 높은 수준의 비즈니스 로직이니까. 같은 계층의 함수들이 일관된 추상화 수준을 가지면 새로운 함수를 어디에 넣을지 쉽게 판단 가능하고, 코드 리뷰할 때 "이 함수가 여기 있는 게 맞나?" 논의가 가능하고, 계층별로 독립적인 테스트가 가능해져.
정리
8장 읽고 기억할 거 세 가지:
- 계층형 설계 = 추상화 수준에 따라 함수를 계층으로 나누기. 아래층은 기본 연산, 위층은 비즈니스 로직
- 직접 구현 = 같은 추상화 수준의 연산으로 표현. 너무 구체적인 세부사항이 보이면 도우미 함수로 추출
- 호출 그래프의 화살표는 항상 아래를 향해야 한다. 위에서 아래로 읽히는 구조가 좋은 설계