계층형 설계와 재귀
코드의 구조를 어떻게 설계할 것인가는 함수형 프로그래밍에서도 중요한 주제입니다. 이 글에서는 계층형 설계 패턴과 재귀 함수를 다룹니다.
소프트웨어 설계란?
섹션 제목: “소프트웨어 설계란?”코드를 만들고, 테스트하고, 유지보수하기 쉬운 프로그래밍 방법을 선택하기 위해 미적 감각을 사용하는 것입니다.
설계 시 고려 요소
섹션 제목: “설계 시 고려 요소”| 함수 본문 | 계층 구조 | 함수 시그니처 |
|---|---|---|
| 길이 | 화살표 길이 | 함수명 |
| 복잡성 | 응집도 | 인자 이름 |
| 구체화 단계 | 구체화 단계 | 인잣값 |
| 함수 호출 | 리턴값 | |
| 프로그래밍 언어의 기능 사용 |
설계 활동의 분류
섹션 제목: “설계 활동의 분류”- 조직화: 새로운 함수를 어디에 놓을지 결정, 함수를 다른 곳으로 이동
- 구현: 구현 바꾸기, 함수 추출하기, 데이터 구조 바꾸기
- 변경: 새 코드를 작성할 곳 선택, 적절한 수준의 구체화 단계 결정
계층형 설계 패턴
섹션 제목: “계층형 설계 패턴”1. 직접 구현
섹션 제목: “1. 직접 구현”한 계층의 함수가 바로 아래 계층의 함수만 호출하도록 합니다.
// Before - 여러 추상화 수준이 섞여 있음function setPriceByName(cart, name, price) { const cartCopy = cart.slice(); for (let i = 0; i < cartCopy.length; i++) { if (cartCopy[i].name === name) { cartCopy[i] = setPrice(cartCopy[i], price); } } return cartCopy;}// After - 같은 수준의 추상화로 구현function indexOfItem(cart, name) { for (let i = 0; i < cart.length; i++) { if (arrayGet(cart, i).name === name) return i; } return null;}
function arraySet(array, idx, value) { const copy = array.slice(); copy[idx] = value; return copy;}
function arrayGet(cart, i) { const copy = [...cart]; return copy[i];}
function setPriceByName(cart, name, price) { const idx = indexOfItem(cart, name); if (idx !== null) { const item = arrayGet(cart, idx); cart = arraySet(cart, idx, setPrice(item, price)); } return cart;}2. 추상화 벽
섹션 제목: “2. 추상화 벽”세부 구현을 감춘 함수로 이루어진 계층입니다. 반복문이나 배열을 직접 다루는 코드를 추상화를 통해 감출 수 있습니다.
- 라이브러리나 API를 만드는 것과 비슷
- **“어떤 것을 신경 쓰지 않아도 되지?”**를 모아 함수로 만든 느낌
- 추상화 벽에 코드를 만드는 것은 계약과 비슷 (새로운 함수를 만들 때 용어를 맞춰야 하는 등의 시간 소요)
3. 작은 인터페이스
섹션 제목: “3. 작은 인터페이스”추상화 벽에 가능한 적은 수의 함수를 노출합니다.
4. 편리한 계층
섹션 제목: “4. 편리한 계층”실용적인 관점에서 편리하게 사용할 수 있는 계층을 설계합니다.
계층별 특성
섹션 제목: “계층별 특성”유지보수성
섹션 제목: “유지보수성”가장 상단에 있는 것이 바꾸기 쉽습니다. → 자주 바뀌는 코드는 가능한 위쪽에 있어야 합니다.
테스트 가능성
섹션 제목: “테스트 가능성”가장 하단에 있는 함수를 테스트하는 것이 효율적입니다. → 영향을 주는 곳이 많기 때문에 한 번의 테스트로 많은 부분을 확인 가능합니다.
재사용성
섹션 제목: “재사용성”하단에 있을수록 재사용성이 높습니다. → 낮은 수준의 단계로 함수를 빼낼수록 재사용성이 높아집니다.
재귀 함수 (Recursive Function)
섹션 제목: “재귀 함수 (Recursive Function)”재귀 함수는 자기 자신을 호출하는 함수입니다. 함수형 프로그래밍에서는 반복문 대신 재귀를 사용하는 경우가 많습니다.
팩토리얼
섹션 제목: “팩토리얼”const factorial = function (num) { if (num === 1) { return 1; } return num * factorial(num - 1);};
factorial(5); // 120배열 생성
섹션 제목: “배열 생성”const countUp = (n) => { if (n < 1) { return []; } else { const countArray = countUp(n - 1); countArray.push(n); return countArray; }};
const array = countUp(5); // [1, 2, 3, 4, 5]재귀 함수의 핵심은 두 가지입니다:
- 종료 조건(Base case): 재귀를 멈추는 조건
- 재귀 단계(Recursive case): 문제를 더 작은 단위로 나누어 자기 자신을 호출
- 계층형 설계는 함수를 추상화 수준별로 배치하여 유지보수성, 테스트 가능성, 재사용성을 높입니다
- 추상화 벽을 통해 세부 구현을 감추면 자료 구조 같은 세부 사항을 신경 쓰지 않아도 됩니다
- 자주 변경되는 코드는 상위 계층에, 재사용되는 코드는 하위 계층에 배치합니다
- 재귀 함수는 종료 조건과 재귀 단계로 구성되며, 반복문을 대체할 수 있습니다
다음 글에서는 Lodash와 Ramda를 활용한 실전 함수형 프로그래밍을 다룹니다.