😀 책에서 기억하고 싶은 내용을 써보세요.
const orderData = orderString.split(/\\s+/);
const productPrice = priceList[orderData[0].split("-")[1]];
const orderPrice = parseInt(orderData[1]) * productPrice;
const orderRecord = parseOrder(order);
const orderPrice = price(orderRecord, priceList);
function parseOrder(aString) {
const values = aString.split(/\\s+/);
return ({
productID: values[0].split("-")[1],
quantity: parseInt(values[1]),
});
}
function price(order, priceList) {
return order.quantity * priceList[order.productID];
}
배경
저자는 서로 다른 두 대상을 한꺼번에 다루는 코드를 발견하면 각각을 별개 모듈로 나누는 방법을 모색한다고 한다.
- 코드를 수정해야 할 때 두 대상을 동시에 생각할 필요 없이 하나에만 집중하기 위해.
- 모듈이 잘 분리되어 있다면 다른 모듈의 상세 내용은 전혀 기억하지 못해도 원하는 대로 수정을 끝마칠 수도 있다.
대표적인 예: 컴파일러
- 컴파일러는 기본적으로 어떤 텍스트(프로그래밍 언어로 작성된 코드)를 입력받아서 실행 가능한 형태(예컨대 특정 하드웨어에 맞는 목적 코드)로 변환한다.
- 컴파일러의 역사가 오래되다 보니 사람들은 컴파일 작업을 여러 단계가 순차적으로 연결된 형태로 분리하면 좋다는 사실을 깨달았다.
- 텍스트를 토큰화
- 토큰을 파싱해서 구문 트리로 만들기
- (최적화 등) 구문 트리를 변환하는 다양한 단계를 거친다.
- 마지막으로 목적 코드(object code)를 생성
각 단계는 자신만의 문제에 집중하기 때문에 나머지 단계에 관해서는 자세히 몰라도 이해할 수 있다.
이렇게 단계를 쪼개는 기법은 주로 덩치 큰 소프트웨어에 적용된다.
절차
- 두 번째 단계에 해당하는 코드를 독립 함수로 추출한다.
- 테스트한다.
- 중간 데이터 구조를 만들어서 앞에서 추출한 함수의 인수로 추가한다.
- 테스트한다.
- 추출한 두 번째 단계 함수의 매개변수를 하나씩 검토한다. 그중 첫 번째 단계에서 사용되는 것은 중간 데이터 구조로 옮긴다. 하나씩 옮길 때마다 테스트한다.
- 간혹 두 번째 단계에서 사용하면 안 되는 매개변수가 있다. 이럴 때는 각 매개변수를 사용한 결과를 중간 데이터 구조의 필드로 추출하고, 이 필드의 값을 설정하는 문장을 호출한 곳으로 옮긴다.
- 첫 번째 단계 코드를 함수로 추출하면서 중간 데이터 구조를 반환하도록 만든다.
- 이때 첫 번째 단계를 변환기 객체로 추출해도 좋다.
예시
상품의 결제 금액을 계산하는 코드
function priceOrder(product, quantity, shippingMethod) {
const basePrice = product.basePrice * quantity;
const discount = Math.max(quantity - product.discountThreshold, 0)
* product.basePrice * product.discountRate;
const shippingPerCase = (basePrice > shippingMethod.discountThreshold)
? shippingMethod.discountedFee : shippingMethod.feePerCase;
const price = basePrice - discount + shippingCost;
return price;
}
위 코드를 두 단계로 나눈다.
- 앞의 몇 줄은 상품 정보를 이용해서 결제 금액 중 상품 가격을 계산한다.
- 뒤의 코드는 배송 정보를 이용하여 결제 금액 중 배송비를 계산한다.
- 먼저 배송비 계산 부분을 함수로 추출한다.
function priceOrder(product, quantity, shippingMethod) {
const basePrice = product.basePrice * quantity;
const discount = Math.max(quantity - product.discountThreshold, 0)
* product.basePrice * product.discountRate;
const price = applyShipping(basePrice, shippingMethod, quantity, discount);
return price;
}
function applyShipping(basePrice, shippingMethod, quantity, discount) {
const shippingPerCase = (basePrice > shippingMethod.discountThreshold)
? shippingMethod.discountedFee : shippingMethod.feePerCase;
const shippingCost = quantity * shippingPerCase;
const price = basePrice - discount + shippingCost;
return price;
}
두 번째 단계에 필요한 데이터를 모두 개별 매개변수로 전달했다.
2. 다음으로 첫 번째 단계와 두 번째 단계가 주고받을 중간 데이터 구조를 만든다.
function priceOrder(product, quantity, shippingMethod) {
const basePrice = product.basePrice * quantity;
const discount = Math.max(quantity - product.discountThreshold, 0)
* product.basePrice * product.discountRate;
const priceData = {}; // <-- 중간 데이터 구조
const price = applyShipping(priceData, basePrice, shippingMethod, quantity, discount);
return price;
}
function applyShipping(priceData, basePrice, shippingMethod, quantity, discount) {
const shippingPerCase = (basePrice > shippingMethod.discountThreshold)
? shippingMethod.discountedFee : shippingMethod.feePerCase;
const shippingCost = quantity * shippingPerCase;
const price = basePrice - discount + shippingCost;
return price;
}
3. 이제 applyShipping()에 전달되는 다양한 매개변수를 살펴보자.
이중 basePrice는 첫 번째 단계를 수행하는 코드에서 생성된다.
따라서 중간 데이터 구조로 옮기고 매개변수 목록에서 제거한다.
function priceOrder(product, quantity, shippingMethod) {
const basePrice = product.basePrice * quantity;
const discount = Math.max(quantity - product.discountThreshold, 0)
* product.basePrice * product.discountRate;
const priceData = {basePrice: basePrice};
const price = applyShipping(priceData, shippingMethod, quantity, discount); // <-- basePrice 제거
return price;
}
function applyShipping(priceData, shippingMethod, quantity, discount) { // <-- basePrice 제거
const shippingPerCase = (priceData.basePrice > shippingMethod.discountThreshold)
? shippingMethod.discountedFee : shippingMethod.feePerCase;
const shippingCost = quantity * shippingPerCase;
const price = priceData.basePrice - discount + shippingCost;
return price;
}
quantity와 discount도 위와 같은 방법으로 저자가 선호하는 중간 데이터 구조에 가존 매개변수 값을 담는 방식을 진행
(코드 생략)
매개변수들을 모두 처리하면 중간 데이터 구조가 완성된다.
4. 이제 첫 번째 단계 코드를 함수로 추출하고 이 데이터 구조를 반환하게 한다.
⇒ 저자는 여기서 최종 결과를 담는 상수들(price)도 갈끔하게 정리했다.
function priceOrder(product, quantity, shippingMethod) {
const priceData = calculatePricingData(product, quantity);
return applyShipping(priceData, shippingMethod);
}
function calculatePricingData(product, quantity) {
const basePrice = product.basePrice * quantity;
const discount = Math.max(quantity- product.discountThreshold, 0)
* product.basePrice * product.discountRate;
return {basePrice: basePrice, quantity: quantity, discount:discount};
}
function applyShipping(priceData, shippingMethod) {
const shippingPerCase = (priceData.basePrice > shippingMethod.discountThreshold)
? shippingMethod.discountedFee : shippingMethod.feePerCase;
const shippingCost = priceData.quantity * shippingPerCase;
return priceData.basePrice - priceData.discount + shippingCost;
}
🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.
- 자주 테스트해야 할 복잡한 동작을 분리함으로써 테스트를 더 쉽게 수행하게 만든다.
- *험블 객체 패턴(Humble Object Pattern)
- 단계를 쪼개면서 단순히 쪼개는 것이 아닌 이전 단계에서 배운 리팩터링 방법을 적용하게 되지 않을까 생각했습니다.
- 단계를 쪼개고 작은 단위의 함수로 나누면서 코드를 전반적으로 수정
- 매개변수의 값을 객체에 데이터 구조에 담아서 전송하는게 상당히 인상깊었습니다.
🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.
- 없음
반응형