[쏙쏙 들어오는 함수형 코딩 Ch8-9]
계층형 설계 stratified design
소프트웨어를 계층으로 구성하는 기술. 각 계층에 있는 함수는 바로 아래 계층의 함수를 이용해 정의
- 장바구니 기능
- 비즈니스 규칙, 특정 서비스만의 규칙:
getFreeShipping()
,cartTax()
- 장바구니를 위한 동작, 도메인 영역에서 공통적으로 적용되는 규칙:
removeItemByName()
,calcTotal()
,addItem()
,setPriceByName()
- copy-on-write:
removeItems()
,addElementLast()
- 언어에서 지원하는 기능:
array.slice()
- 비즈니스 규칙, 특정 서비스만의 규칙:
각 계층을 정확히 구분하기는 어렵지만, 계층을 잘 구분하려면 구분하기 위한 다양한 변수를 찾고, 찾은 것을 가지고 어떻게 해야하는지 알아야 한다
계층형 설계 감각 키우기
입력
함수 본문
- 길이
- 복잡성
- 구체화 단계
- 함수 호출
- 프로그래밍 언어의 기능 사용
계층 구조
- 화살표 길이
- 응집도
- 구체화 단계
함수 시그니처
- 함수명
- 인자 이름
- 인자 값
- 리턴 값
출력
조직화
- 새로운 함수를 어디에 놓을지 결정
- 함수를 다른 곳으로 이동
구현
- 구현 바꾸기
- 함수 추출하기
- 데이터 구조 바꾸기
변경
- 새 코드를 작성할 곳 선택하기
- 적절한 수준의 구체화 단계 결정하기
계층형 설계 패턴
패턴 1: 직접 구현
- 한 단계 구체화 수준에 관한 문제만 해결
- 직접 구현된 함수를 읽을 때, 함수 시그니처가 나타내고 있는 문제를 함수 본문에서 적절한 구체화 수준에서 해결해야 한다
- 너무 구체적이라면 코드에서 나는 냄새임
- 함수가 더 구체적인 내용을 다루지 않도록 함수를 일반적인 함수로 빼내기
- 일반적인 함수가 많을수록 재사용성이 높아짐
패턴 2: 추상화 벽 abstraction barrier
- 중요한 세부 구현을 감추고 인터페이스를 제공
- 추상화 벽 위 계층에 있는 함수들은 그 아래 계층 함수들이 사용하는 데이터 구조를 몰라도 됨
- 마케팅 관련 코드는 장바구니가 어떻게 구현되어 있는지 알 필요가 없다
- 인터페이스를 활용하면 고차원으로 생각할 수 있고,
- 고수준의 추상화 단계만 생각하면 되기 때문에 두뇌 용량의 한계를 극복할 수 있음
- 추상화 벽 위 계층에 있는 함수들은 그 아래 계층 함수들이 사용하는 데이터 구조를 몰라도 됨
- 변경사항이 있을때, 동일한 추상화 레벨에 있는 함수들만 변경하면 됨
- 👉 추상화 벽을 작게 만들어야 하는 이유 중 하나; 구현이 변경되었을 때 고쳐야 할 것이 적음
활용
- 쉽게 구현을 바꾸기 위해
- 프로토타이핑, API 임시 데이터 등 구현에 대한 확신이 없는 경우
- 다만 만약을 대비해 코드를 만드는 것은 좋지 않은 습관이므로 주의
- 바뀌지 않을지도 모르는 코드를 언젠가 쉽게 바꿀 수 있게 만들려는 함정에 빠지지 말자
- 대부분 데이터 구조는 바뀌지 않는다
- 추상화 벽은 코드를 쉽게 고치려고 사용하는 것이 아니라, 신경 쓰지 않아도 되는 것을 다루는 것이 핵심
- 코드를 읽고 쓰기 쉽게 만들기 위해
- 때로는 구체적인 것이 버그를 만든다
- 반복문 off-by-one 에러 등
- 때로는 구체적인 것이 버그를 만든다
- 팀 간 조율해야 할 것을 줄이기 위해
- 주어진 문제에 집중하기 위해
패턴 3: 작은 인터페이스 minimal interface
- 새로운 기능을 추가할 때, 하위 계층에 기능을 추가하거나 고치는 것보다 상위 계층에 만드는 것
- 시스템이 커질수록 비즈니스 개념을 나타내는 중요한 인터페이스는 작고 강력한 동작으로 구성하는 것이 좋다
- 이상적인 계층은 최소한의 필요한 함수만 갖고 있고, 함수는 바뀌지 않아야 함
- 현실적으로는 불가능하지만, 이 목표에 가려고 노력해야
- 함수 목적에 맞는 계층이 어디인지 찾는 감각을 기르는 것이 가장 중요
- 하려는 일이 적은 함수로 잘할 수 있는가?
- 목적에 맞게 바꾸려고 하고 있는가?
패턴 4: 편리한 계층 comfortable layers
- 코드와 그 추상화 계층은 작업할 때 편리해야 하고, 비즈니스 문제를 잘 풀 수 있어야 함
- 그냥 좋아서 계층을 추가하면 안된다
- 강력한 추상화 계층은 만들기 어렵고, 시간이 지나면 크게 도움이 되지 않는다고 느낄 것
- 언제 패턴을 적용하고 언제 멈춰야 하는가?
- 지금 편리한가? 그렇다면 설계는 조금 멈춰도 된다
- 구체적인 것을 너무 많이 알아야 하거나, 코드가 지저분하게 느껴지는가? 그렇다면 패턴을 적용하라
ex. 넥타이 하나를 사면 무료로 넥타이 클립을 하나 주는 코드
유지보수 하기 어려운 코드
function freeTieClip(cart) {
let hasTie = false;
let hasTieClip = false;
for (let i = 0; i < cart.length; i++) {
const item = cart[i];
if (item.name === 'tie') hasTie = true;
if (item.name === 'tie clip') hasTieClip = true;
}
if (hasTie && !hasTieClip) {
const tieClip = makeItem('tie clip', 0);
return addItem(cart, tieClip);
}
return cart;
}
- ‘직접 구현’ 패턴을 따르지 않았기 때문
- 마케팅 캠페인에 관련된 함수가 장바구니가 배열이라는 사실을 알 필요가 없다
- 장바구니 배열을 돌다가 off-by-one 에러가 생기면 실패할 수 있다
코드 개선하기
직접 구현 패턴 적용하기
저수준 코드 추출하기
- 장바구니 안에 제품이 있는지 확인하는 함수가 있다면, 저수준 반복문을 직접 쓰지 않아도 됨
호출 그래프를 만들어 함수 호출 시각화하기
- 함수에서 사용하는 다른 함수, 언어 기능을 호출 그래프로 시각화
- 해당 함수/기능들의 추상화 수준이 같은지 확인
- 한 함수에서 서로 다른 추상화 단계를 사용하면 코드가 명확하지 않아 읽기 어렵다
- 다른 함수:
makeItem
,addItem
- 언어 기능: array index,
for
loop
- 저수준 코드를 함수로 분리
isInCart
- 개선된 함수
freeTieClip
에서는 장바구니가 배열인지 몰라도 됨 => 내부에서 사용하는 함수들이 모두 비슷한 계층에 있다는 것 - 모든 함수가 그래프에 있어야 하고, 화살표는 옆이나 위가 아닌 아래로 향해야 함

- 한 함수의 모든 화살표는 같은 길이를 가져야 한다
- 화살표가 복잡한 이유는 코드가 정돈되어 있지 않기 때문
- 한 함수에서 여러 계층을 사용하지 않도록 정리해야 함
- 경우에 따라 개선을 하더라도 화살표 길이가 여전히 동일하지 않을 수 있지만, 화살표 수를 줄이거나 길이가 좀더 줄어든다면 괜찮다
비기능적 요구사항 nonfunctional requirements
- 호출 그래프를 통해 알 수 있는 것
- 소프트웨어 설계를 하는 중요한 이유
유지보수성 maintainability
자주 바뀌는 코드는 가능한 위쪽에 있어야
- 다른 코드에 영향을 주지 않는 코드
- 자주 바뀌므로 적게 유지하는 것이 좋다
테스트성 testability
시간이 지나도 변하지 않는 코드는 가장 아래 계층에 있어야
- copy-on-write 함수 등 한번 잘 만들어 두면 바꿀 일이 없음
- 테스트가 중요
- 위에 있는 코드보다 더 안정적이고 오래 가야 하므로
- 잘 만든 코드, 테스트는 자주 고칠 필요도 없음
재사용성 reusability
낮은 수준으로 함수를 빼내면 재사용성이 더 높아짐
- 아래에 있는 코드가 재사용하기 더 좋음
#develop #fp #architecture #stratified_design