😀 책에서 기억하고 싶은 내용을 써보세요.

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];
}

 

배경

저자는 서로 다른 두 대상을 한꺼번에 다루는 코드를 발견하면 각각을 별개 모듈로 나누는 방법을 모색한다고 한다.

  • 코드를 수정해야 할 때 두 대상을 동시에 생각할 필요 없이 하나에만 집중하기 위해.
  • 모듈이 잘 분리되어 있다면 다른 모듈의 상세 내용은 전혀 기억하지 못해도 원하는 대로 수정을 끝마칠 수도 있다.

 

대표적인 예: 컴파일러

  • 컴파일러는 기본적으로 어떤 텍스트(프로그래밍 언어로 작성된 코드)를 입력받아서 실행 가능한 형태(예컨대 특정 하드웨어에 맞는 목적 코드)로 변환한다.
  • 컴파일러의 역사가 오래되다 보니 사람들은 컴파일 작업을 여러 단계가 순차적으로 연결된 형태로 분리하면 좋다는 사실을 깨달았다.
    1. 텍스트를 토큰화
    2. 토큰을 파싱해서 구문 트리로 만들기
    3. (최적화 등) 구문 트리를 변환하는 다양한 단계를 거친다.
    4. 마지막으로 목적 코드(object code)를 생성

각 단계는 자신만의 문제에 집중하기 때문에 나머지 단계에 관해서는 자세히 몰라도 이해할 수 있다.

이렇게 단계를 쪼개는 기법은 주로 덩치 큰 소프트웨어에 적용된다.

 

 

절차

  1. 두 번째 단계에 해당하는 코드를 독립 함수로 추출한다.
  2. 테스트한다.
  3. 중간 데이터 구조를 만들어서 앞에서 추출한 함수의 인수로 추가한다.
  4. 테스트한다.
  5. 추출한 두 번째 단계 함수의 매개변수를 하나씩 검토한다. 그중 첫 번째 단계에서 사용되는 것은 중간 데이터 구조로 옮긴다. 하나씩 옮길 때마다 테스트한다.
    • 간혹 두 번째 단계에서 사용하면 안 되는 매개변수가 있다. 이럴 때는 각 매개변수를 사용한 결과를 중간 데이터 구조의 필드로 추출하고, 이 필드의 값을 설정하는 문장을 호출한 곳으로 옮긴다.
  6. 첫 번째 단계 코드를 함수로 추출하면서 중간 데이터 구조를 반환하도록 만든다.
    • 이때 첫 번째 단계를 변환기 객체로 추출해도 좋다.

 

예시

상품의 결제 금액을 계산하는 코드

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;
}

위 코드를 두 단계로 나눈다.

  • 앞의 몇 줄은 상품 정보를 이용해서 결제 금액 중 상품 가격을 계산한다.
  • 뒤의 코드는 배송 정보를 이용하여 결제 금액 중 배송비를 계산한다.

 

  1. 먼저 배송비 계산 부분을 함수로 추출한다.
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)
  • 단계를 쪼개면서 단순히 쪼개는 것이 아닌 이전 단계에서 배운 리팩터링 방법을 적용하게 되지 않을까 생각했습니다.
    • 단계를 쪼개고 작은 단위의 함수로 나누면서 코드를 전반적으로 수정
  • 매개변수의 값을 객체에 데이터 구조에 담아서 전송하는게 상당히 인상깊었습니다.

 

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 없음

 

 

반응형

+ Recent posts