리팩터링 정리 Chapter 6 - 기본적인 리팩터링

김기욱·2022년 3월 17일
0

TIL

목록 보기
10/11

6.1 함수 추출하기

절차

  1. 함수를 새로 만들고 목적을 잘 드러내는 이름을 붙인다.('어떻게'가 아닌 '무엇을'하는지가 드러나야 한다.)
  2. 추출할 코드를 원본 함수에서 복사하여 새 함수에 붙여넣는다.
  3. 추출한 코드 중 원본 함수의 지역 변수를 참조하거나 추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사한다. 있다면 매개변수로 전달한다.
  4. 변수를 다 처리했다면 컴파일한다.
  5. 원본 함수에서 추출한 코드 부분을 새로 만든 함수를 호출하는 문장으로 바꾼다(즉, 추출한 함수로 일을 위임한다.)
  6. 테스트한다.
  7. 다른 코드에 방금 추출한 것과 똑같거나 비슷한 코드가 없는지 살핀다. 있다면 방금 추출한 새 함수를 호출하도록 바꿀지 검토한다.

예시: 유효범위를 벗어나는 변수가 없을 때

function printOwing(invoice) {
  let outstanding = 0;

  // 미해결 채무(outstanding)를 계산한다.
  for (const o of invoice.orders) {
    outstanding += o.amount
  }

  // 마감일(dueDate)을 기록한다.
  const today = Clock.today;
  invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);

  // 세부 사항을 출력한다.
  console.log(`고객명: ${invoice.customer}`)
  console.log(`채무액: ${outstanding}`)
  console.log(`마감일: ${invoice.dueDate.toLocaleDateString()}`)
}

해당 코드를 잘라내서 새 함수에 붙이고, 원래 자리에 새 함수 호출문을 넣는다.

function printOwing(invoice) {
  let outstanding = 0;

  // 미해결 채무(outstanding)를 계산한다.
  for (const o of invoice.orders) {
    outstanding += o.amount;
  }

  // 마감일(dueDate)을 기록한다.
  const today = Clock.today;
  invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);

  printDetails();

  function printDetails() {
    console.log(`고객명: ${invoice.customer}`)
    console.log(`채무액: ${outstanding}`)
    console.log(`마감일: ${invoice.dueDate.toLocaleDateString()}`)
  }
}

예시: 지역 변수를 사용할 때

function printOwing(invoice) {
  let outstanding = 0;

  // 미해결 채무(outstanding)를 계산한다.
  for (const o of invoice.orders) {
    outstanding += o.amount;
  }

  // 마감일(dueDate)을 기록한다.
  const today = Clock.today;
  invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);

  printDetails();

  function printDetails() {
    console.log(`고객명: ${invoice.customer}`)
    console.log(`채무액: ${outstanding}`)
    console.log(`마감일: ${invoice.dueDate.toLocaleDateString()}`)
  }
}

변수를 사용하지만 다른 값을 다시 대입하지는 않을 때는, 지역 변수들을 그냥 매개변수로 넘긴다.

function printOwing(invoice) {
  let outstanding = 0;

  // 미해결 채무(outstanding)를 계산한다.
  for (const o of invoice.orders) {
    outstanding += o.amount;
  }
	
  recordDueDate(invoice); // 마감일을 기록한다.
  printDetails(invoice, outstanding); // 세부사항을 출력한다.
}

function printDetails(invoice, outstanding) {
  console.log(`고객명: ${invoice.customer}`)
  console.log(`채무액: ${outstanding}`)
  console.log(`마감일: ${invoice.dueDate.toLocaleDateString()}`)
}

function recordDueDate(invoice) {
  const today = Clock.today;
  invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
}

예시: 지역 변수의 값을 변경할 때

function printOwing(invoice) {
  const outstanding = calculateOutstanding(invoice); // 미해결 채무(outstanding)를 계산한다.
  recordDueDate(invoice); // 마감일을 기록한다.
  printDetails(invoice, outstanding); // 세부사항을 출력한다.
}

function calculateOutstanding(invoice) {
  let result = 0;
  for (const o of invoice.orders) {
    result += o.amount;
  }
  return result
}

function printDetails(invoice, outstanding) {
  console.log(`고객명: ${invoice.customer}`)
  console.log(`채무액: ${outstanding}`)
  console.log(`마감일: ${invoice.dueDate.toLocaleDateString()}`)
}

function recordDueDate(invoice) {
  const today = Clock.today;
  invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
}

값을 반환할 변수가 여러 개라면?

추출할 코드를 다르게 재구성하는 방향으로 처리한다.
기본적으로 함수가 값 하나만을 방식이 심플하므로 각각을 반환하는 함수 여러 개로 만든다.
굳이 한 함수에서 여러 값을 반환해야 한다면 값들을 레코드로 묶어서 반환해도 되지만, 임시변수를 질의 함수로 바꾸거나 변수를 쪼개는 식으로 처리하면 좋다.

6.2 함수 인라인하기

절차

  1. 다형 메서드인지 확인한다.
    -> 서브클래스에서 오버라이드하는 메서드는 인라인하면 안 된다.
  2. 인라인할 함수를 호출하는 곳을 모두 찾는다.
  3. 각 호출문을 함수 본문으로 교체한다.
  4. 하나씩 교체 할 때마다 테스트한다.
    -> 인라인 작업을 한 번에 처리할 필요는 없다. 인라인하기가 까다로운 부분이 있다면 일단 남겨두고 여유가 생길 때마다 틈틈이 처리한다.
  5. 함수 정의(원래함수)를 삭제한다.

예시

function reportLines(aCustomer) {
  const lines = [];
  gatherCustomerData(lines, aCustomer);
  return lines;
}

function gatherCustomerData(out, aCustomer) {
  out.push(["name", aCustomer.name]);
  out.push(["location", aCustomer.location]);
function reportLines(aCustomer) {
  const lines = [];
  lines.push(["name", aCustomer.name]);
  lines.push(["location", aCustomer.location]);
  return lines;
}

6.3 변수 추출하기

절차

  1. 추출하려는 표현식에 부작용은 없는지 확인한다.
  2. 불변 변수를 하나 선언하고 이름으 붙일 표현식의 복제본을 대입한다.
  3. 원본 표현식을 새로 만든 변수로 교체한다./
  4. 테스트한다.
  5. 표현식을 여러 곳에서 사용한다면 각각을 새로 만든 변수로 교체한다. 하나 교체할 때마다 테스트 한다.

예시

class Order {
  constructor(aRecord) {
    this._data = aRecord;
  }
  
  get quantity() {return this._data.quantity;}
  get itemPrice() {return this._data.itemPrice;}
  
  get price() {
    return this.quantity * this.itemPrice
    - Math.max(0, this.quantity - 500) * this.itemPrice * 0.05
    + Math.min(this.quantity * this.itemPrice * 0.1, 100);
  }

클래스 안에서 추출하려는 이름이 가격을 계산하는 price() 메서드의 범위를 넘어 주문을 표현하는 Order클래스 전체에 적용된다. 이처럼 클래스 전체에 영향을 줄 때 변수가 아닌 메서드로 추출한다.

class Order {
  constructor(aRecord) {
    this._data = aRecord;
  }
  
  get quantity() {return this._data.quantity;}
  get itemPrice() {return this._data.itemPrice;}
  
  get price() {
    return basePrice - quantityDiscount + shipping;
  }
  get basePrice() {return this.quantity * this.itemPrice;}
  get quantityDiscount() {return Math.max(0, this.quantity - 500) * this.itemPrice * 0.05;}
  get shipping() {return Math.min(this.quantity * this.itemPrice * 0.1, 100);}

6.4 변수 인라인하기

절차

  1. 대입문의 우변(표현식)에서 부작용이 생기지는 않는지 확인한다.
  2. 변수가 불변으로 선언되지 않았다면 불변으로 만든 후 테스트한다.
    -> 이렇게 하면 변수에 값이 단 한 번만 대입되는지 확인할 수 있다.
  3. 이 변수를 가장 처음 사용하는 코드를 찾아서 대입문 우변의 코드로 바꾼다.
  4. 테스트한다.
  5. 변수를 사용하는 부분을 모두 교체할 때까지 이 과정을 반복한다.
  6. 변수 선언문과 대입문을 지운다.
  7. 테스트한다.

6.5 함수 선언 바꾸기

간단한 절차

  1. 매개변수를 제거하려거든 먼저 함수 본문에서 제거 대상 매개변수를 참조하는 곳은 없는지 확인한다.
  2. 메서드 선언을 원하는 형태로 바꾼다.
  3. 기존 메서드 선언을 참조하는 부분을 모두 찾아서 바뀐 형태로 수정한다.
  4. 테스트한다.

마이그레이션 절차

  1. 이어지는 추출 단계를 수월하게 만들어야 한다면 함수의 본문을 적절히 리팩터링한다.
  2. 함수 본문을 새로운 함수로 추출한다.
    -> 새로 만들 함수 이름이 기존 함수와 같다면 일단 검색하기 쉬운 이름을 임시로 붙여둔다.
  3. 추출한 함수에 매개변수를 추가해야 한다면 '간단한 절차'를 따라 추가한다.
  4. 테스트한다.
  5. 기존 함수를 인라인 한다.
  6. 이름을 임시로 붙여뒀다면 함수 선언 바꾸기를 한 번 더 적용해서 원래 이름으로 되돌린다.
  7. 테스트한다.

예시: 매개변수 추가하기

let reservations = [];

function addReservation(customer) {
  zz_addReservation(customer);
}

function zz_addReservation(customer) {
  reservations.push(customer);
}

자바스크립트로 프로그래밍한다면, 호출문을 변경하기 전에 어서션을 추가하여 호출 하는 곳에서 새로 추가한 매개변수를 실제로 사용하는지 확인한다.

let reservations = [];

function addReservation(customer, isPriority) {
  zz_addReservation(customer, isPriority);
}

function zz_addReservation(customer, isPriority) {
  assert(isPriority === true || isPriority === false);
  reservations.push(customer);
}

이렇게 해두면 호출문을 수정하는 과정에서 실수로 새 매개변수를 빠뜨린 부분을 찾는데 도움이 된다.

let reservations = [];

function addReservation(customer, isPriority) {
  reservations.push(customer);
}

예시: 매개변수를 속성으로 바꾸기

function inNewEngland(aCustomer) {
  return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(aCustomer.address.state);
}

const newEnglander = someCustomers.filter(c => inNewEngland(c));

주 식별 코드를 매개변수로 받도록 리팩터링 -> 고객에 대한 의존성이 제거되어 더 넓은 문맥에 활용가능

function isNewEngland(stateCode) {
  return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);
}

const newEnglander = someCustomers.filter(c => isNewEngland(c.address.state));

6.6 변수 캡슐화하기

절차

  1. 변수로의 접근과 갱신을 전담하는 캡슐화 함수들을 만든다.
  2. 정적 검사를 수행한다.
  3. 변수를 직접 참조하던 부분을 모두 적절한 캡슐화 함수 호출로 바꾼다. 하나씩 바꿀 때마다 테스트 한다.
  4. 변수의 접근 범위를 제한한다.
    -> 변수로의 직접 접근을 막을 수 없을 때도 있다. 그럴 때는 변수 이름을 바꿔서 테스트해보면 해당 변수를 참조하는 곳을 쉽게 찾아낼 수 있다.
  5. 테스트 한다.
  6. 변수 값이 레코드라면 레코드 캡슐화하기를 적용할지 고려해본다.

예시

let defaultOwner = { firstName: '마틴', lastName: '파울러' }

defaultOwner = { firstName: '레베카', lastName: '파슨스' }
spaceship.owner = defaultOwner;

데이터를 읽고 쓰는 함수부터 정의한다.(게터,세터) 그런 다음 게터와 세터함수를 쓰도록 바꾼다.

let defaultOwner = { firstName: '마틴', lastName: '파울러' }
export function getDefaultOwner() {return defaultOwner;}
export function setDefaultOwner() {defaultOwner = arg;}

setDefaultOwner()
spaceship.owner = getDefaultOwner();

6.7 변수 이름 바꾸기

절차

  1. 폭넓게 쓰이는 변수라면 변수 캡슐화하기를 고려한다.
  2. 이름을 바꿀 변수를 참조하는 곳을 모두 찾아서, 하나씩 변경한다.
    -> 다른 코드베이스에서 참조하는 변수는 외부에 공개된 변수이므로 이 리팩터링을 적용할 수 없다.
    -> 변수 값이 변하지 않는다면 다른 이름으로 복제본을 만들어서 하나씩 점진적으로 변경한다. 하나씩 바꿀때마다 테스트한다.
  3. 테스트한다.

예시: 변수 캡슐화하기

result += `<h1>${title()}</h1>`;

setTitle(obj['articleTitle']);

function title() {return tpHd;}
function setTitle(arg) {tpHd = arg;}

예시: 상수 이름 바꾸기

const cpyNm = '애크미 구스베리';

먼저 원본의 이름을 바꾼 후, 원본의 원래 이름(기존 이름)과 같은 복제본을 만든다.

const companyNm = '애크미 구스베리';
const cpyNm = companyNm;

이제 기존 이름(복제본)을 참조하는 코드들을 새 이름으로 점진적으로 바꿀 수 있다. 다 바꿨다면 복제본을 삭제한다.

6.8 매개변수 객체 만들기

절차

  1. 적당한 데이터 구조가 아직 마련되어 있지 않다면 새로 만든다.
    -> 개인적으로 클래스로 만드는 걸 선호한다. 나중에 동작까지 함께 묶기 좋기 때문이다. 나는 주로 데이터 구조를 값 객체로 만든다.
  2. 테스트한다.
  3. 함수 선언 바꾸기로 새 데이터 구조를 매개변수로 추가한다.
  4. 테스트한다.
  5. 함수 호출 시 새로운 데이터 구조 인스턴스를 넘기도록 수정한다. 하나씩 수정할 때마다 테스트한다.
  6. 기존 매개변수를 사용하던 코드를 새 데이터 구조의 원소를 사용하도록 바꾼다.
  7. 다 바꿨다면 기존 매개변수를 제거하고 테스트한다.

예시

const station = { 
  name: "ZB1",
  readings: [
    {temp: 47, time: "2016-11-10 09:10"},
    {temp: 53, time: "2016-11-10 09:20"},
    {temp: 58, time: "2016-11-10 09:30"},
    {temp: 53, time: "2016-11-10 09:40"},
    {temp: 51, time: "2016-11-10 09:50"},
   ]
}

// 정상 범위를 벗어난 측정값을 찾는 함수
function readingsOutsideRange(station, min, max) {
  return station.readings.filter(r => r.temp < min || r.temp > max);
}

alters = readingsOutsideRange(
  station, 
  operationPlan.temperatureFloor, // 최저 온도
  operationPlan.temperatureCeiling // 최고 온도
);

묶은 데이터를 표현하는 클래스부터 선언한다.

const station = { 
  name: "ZB1",
  readings: [
    {temp: 47, time: "2016-11-10 09:10"},
    {temp: 53, time: "2016-11-10 09:20"},
    {temp: 58, time: "2016-11-10 09:30"},
    {temp: 53, time: "2016-11-10 09:40"},
    {temp: 51, time: "2016-11-10 09:50"},
   ]
}

class NumberRange {
  constructor(min, max) {
    this._data = {min: min, max: max};
  }
  get min() {return this._data.min;}
  get max() {return this._data.max;}
}

// 정상 범위를 벗어난 측정값을 찾는 함수
function readingsOutsideRange(station, range) {
  return station.readings.filter(r => r.temp < range.min || r.temp > range.max);
}
  
const range = new NumberRange(operationPlan.temperatureFloor, operationPlan.temperatureCeiling)

alters = readingsOutsideRange(station, range);

클래스를 만들어두면 관련 동작들을 이 클래스로 옮길 수 있다는 이점이 생긴다.

const station = { 
  name: "ZB1",
  readings: [
    {temp: 47, time: "2016-11-10 09:10"},
    {temp: 53, time: "2016-11-10 09:20"},
    {temp: 58, time: "2016-11-10 09:30"},
    {temp: 53, time: "2016-11-10 09:40"},
    {temp: 51, time: "2016-11-10 09:50"},
   ]
}

class NumberRange {
  constructor(min, max) {
    this._data = {min: min, max: max};
  }
  get min() {return this._data.min;}
  get max() {return this._data.max;}
  // 온도가 허용 범위 안에 있는지 검사하는 메서드
  contains(arg) {return (arg >= this.min && arg<= this.max)}
}

// 정상 범위를 벗어난 측정값을 찾는 함수
function readingsOutsideRange(station, range) {
  return station.readings.filter(r => !range.contains(r.temp));
}
  
const range = new NumberRange(operationPlan.temperatureFloor, operationPlan.temperatureCeiling)

alters = readingsOutsideRange(station, range);

6.9 여러 함수를 클래스로 묶기

절차

  1. 함수들이 공유하는 공통 데이터 레코드를 캡슐화한다.
    -> 공통 데이터가 레코드 구조로 묶여 있지 않다면 먼저 매개변수 객체 만들기로 데이터를 하나로 묶는 레코드를 만든다.
  2. 공통 레코드를 사용하는 함수 각각을 새 클래스로 옮긴다(함수 옮기기)
    -> 공통 레코드의 멤버는 함수 호출문의 인수 목록에서 제거한다.
  3. 데이터를 조작하는 로직들은 함수로 추출해서 새 클래스로 옮긴다.

예시: 장기렌트카 요금 부과하기

// 클라이언트1
const aReading = acquireReading()
const baseCharge = baseRate(aReading.month, aReading.year) * aReading.quantity

// 클라이언트2
const aReading = acquireReading()
const base = baseRate(aReading.month, aReading.year) * aReading.quantity
const taxableCharge = Math.max(0, base - taxThreshold(aReading.year));

// 클라이언트3
const aReading = acquireReading()
const basicChargeAmount = calculateBaseCharge(aReading);

function calculateBaseCharge(aReading) {
  return baseRate(aReading.month, aReading.year) * aReading.quantity
}

앞선 두 클라이언트(1,2)도 클라이언트3이 쓰는 함수를 사용하도록 고치려고 한다. 하지만 이렇게 최상위 함수로 두면 못 보고 지나치기 쉽다는 문제가 있다. 나라면 이런 함수를 데이터 처리 코드 가까이에 둔다. 그러기 위한 좋은 방법으로, 데이터를 클래스로 만들 수 있다.

class Reading {
  constructor(data) {
    this._customer = data.customer;
    this._quantity = data.quantity;
    this._month = data.month;
    this._year = data.year;
  }
  get customer() {return this._customer;}
  get quantity() {return this._quantity;}
  get month() {return this._month;}
  get year() {return this._year;}
}

이미 만들어져 있는 calculateBaseCharge()부터 옮기자. 새 클래스를 사용하려면 데이터를 얻자마자 객체로 만들어야 한다.

// 클라이언트3
const rawReading = acquireReading();
const aReading = new Reading(rawReading);
const basicChargeAmount = calculateBaseCharge(aReading);

그런 다음 calcualteBaseCharge()를 새로만든 클래스에 옮긴다.

class Reading {
  constructor(data) {
    this._customer = data.customer;
    this._quantity = data.quantity;
    this._month = data.month;
    this._year = data.year;
  }
  get customer() {return this._customer;}
  get quantity() {return this._quantity;}
  get month() {return this._month;}
  get year() {return this._year;}
  get baseCharge() {return baseRate(this.month, this.year) * this.quantity;}
}

클래스가 적용된 클라이언트3

// 클라이언트3
const rawReading = acquireReading();
const aReading = new Reading(rawReading);
const basicChargeAmount = aReading.baseCharge;

세금계산 하는 함수도 역시 클래스메서드로 변환할 수 있다.

class Reading {
  constructor(data) {
    this._customer = data.customer;
    this._quantity = data.quantity;
    this._month = data.month;
    this._year = data.year;
  }
  get customer() {return this._customer;}
  get quantity() {return this._quantity;}
  get month() {return this._month;}
  get year() {return this._year;}
  get baseCharge() {return baseRate(this.month, this.year) * this.quantity;}
  get texableCharge() {return MMath.max(0, this.baseCharge - taxThreshold(this.year))
}

파생 데이터 모두를 필요한 시점에 계산되게 만들었으니 저장된 데이터를 갱신하더라도 문제가 생기지 않는다.

// 클라이언트3
const rawReading = acquireReading();
const aReading = new Reading(rawReading);
const taxableCharge = aReading.texableCharge;

6.10 여러 함수를 변환 함수로 묶기

절차

  1. 변환할 레코드를 입력받아서 값을 그대로 반환하는 변환 함수를 만든다
    -> 이 작업은 대체로 깊은 복사로 처리해야 한다. 변환 함수가 원본 레코드를 바꾸지 않는지 검사하는 테스트를 마련해두면 도움될때가 많다.
  2. 묶을 함수 중 함수 하나를 골라서 본문 코드를 변환 함수로 옮기고, 처리 결과를 레코드에 새 필드로 기록한다. 그런 다음 클라이언트 코드가 이 필드를 사용하도록 수정한다.
  3. 테스트한다.
  4. 나머지 관련 함수도 위 과정에 따라 처리한다.

예시: 장기렌트카 요금 부과하기

function enrichReading(original) {
  const result = _.cloneDeep(original);
  result.baseCharge = calculateBaseCharge(result);
  result.texableCharge = Math.max(0, result.baseCharge - taxThreshold(result.year));
  return result;
}

// 클라이언트3
const rawReading = acquireReading();
const aReading = enrichReading(rawReading);
const taxableCharge = aReading.texableCharge;

테스트에 성공하면 texableCharge 변수도 인라인한다.

6.11 단계 쪼개기

절차

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

예시

상품의 결제 금액을 계산하는 코드로 시작해보자

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 shippingCost = quantity * shippingPerCase;
  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 priceData = {basePrice: basePrice};
  const price = applyShipping(priceData, shippingMethod, quantity, discount);
  return price;
}

function applyShipping(priceData, shippingMethod, quantity, discount) {
  const shippingPerCase = (priceData.basePrice > shippingMethod.discountThreshold) 
  ? shippingMethod.discountedFee : shippingMethod.feePerCase;
  const shippingCost = quantity * shippingPerCase;
  const price = priceData.basePrice - discount + shippingCost;
  return price;
}

quantity의 경우 첫 번째 단계에서 사용하지만 거기서 생성된 것은 아니다. 그래서 매개변수로 나둬도 되지만 중간 데이터에 옮겨보자. discount도 마찬가지다.

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, 
    quantity: quantity, 
    discount: discount 
  };
  const price = applyShipping(priceData, shippingMethod, quantity, discount);
  return price;
}

function applyShipping(priceData, shippingMethod) {
  const shippingPerCase = (priceData.basePrice > shippingMethod.discountThreshold) 
  ? shippingMethod.discountedFee : shippingMethod.feePerCase;
  const shippingCost = priceData.quantity * shippingPerCase;
  const price = priceData.basePrice - priceData.discount + shippingCost;
  return 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; // 변수인라인
}
profile
어려운 것은 없다, 다만 아직 익숙치않을뿐이다.

0개의 댓글