데이터 조직화

niyu·2022년 1월 21일

리팩터링 2판

목록 보기
8/11
post-thumbnail

변수 쪼개기

example-code-1

하나의 값이 여러 목적으로 사용된다면 변수를 쪼개 용도별로 분리하도록 한다.

📜 절차

  1. 변수를 새로운 이름으로 선언하고 다음번 대입 때까지의 모든 참조를 새 변수명으로 바꾼다. 이때 가능하면 불변으로 선언한다.

다음은 일반적인 물리 법칙을 이용해 전파 거리를 계산한 코드다.

const distanceTravelled = (scenario, time) => {
  let result;
  let acc = scenario.primaryForce / scenario.mass; // 가속도(a) = 힘(F) / 질량(m)
  let primaryTime = Math.min(time, scenario.delay);
  result = 0.5 * acc * primaryTime * primaryTime; // 전파된 거리
  let secondaryTime = time - scenario.delay;
  if (secondaryTime > 0) {
    // 두 번째 힘을 반영해 다시 계산
    let primaryVelocity = acc * scenario.delay;
    acc = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass;
    result +=
      primaryVelocity * secondaryTime +
      0.5 * acc * secondaryTime * secondaryTime;
  }
  return result;
};

acc 변수에 두 번 대입되는 것을 볼 수 있는데 첫 번째는 힘이 유발한 초기 가속도를 저장하는 역할이고, 다른 하나는 두 번째 힘까지 반영된 후의 가속도를 저장하는 역할이다. 이 변수를 쪼개보자.

첫 번째 용도로는 primaryAcceleration 으로 변수 이름을 바꾼다. 또한 값을 다시 대입하지 못하도록 const로 선언한다. 그리고 두 번째 용도로 사용할 acc 변수를 다시 선언한다.

const distanceTravelled = (scenario, time) => {
  let result;
  let primaryAcceleration = scenario.primaryForce / scenario.mass; // 이름 수정
  let primaryTime = Math.min(time, scenario.delay);
  result = 0.5 * primaryAcceleration * primaryTime * primaryTime; // 변수 대입
  let secondaryTime = time - scenario.delay;
  if (secondaryTime > 0) {
    let primaryVelocity = primaryAcceleration * scenario.delay; // 변수 대입
    let acc = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass; // 다시 선언
    result +=
      primaryVelocity * secondaryTime +
      0.5 * acc * secondaryTime * secondaryTime;
  }
  return result;
};

다음으로 두 번째 용도에 적합한 이름으로 수정해 acc 변수를 제거한다.

const distanceTravelled = (scenario, time) => {
  let result;
  let primaryAcceleration = scenario.primaryForce / scenario.mass;
  let primaryTime = Math.min(time, scenario.delay);
  result = 0.5 * primaryAcceleration * primaryTime * primaryTime;
  let secondaryTime = time - scenario.delay;
  if (secondaryTime > 0) {
    let primaryVelocity = primaryAcceleration * scenario.delay;
    const secondaryAcceleration = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass; // 이름 수정, const로 변경 
    result +=
      primaryVelocity * secondaryTime +
      0.5 * secondaryAcceleration * secondaryTime * secondaryTime; // 변수 대입
  }
  return result;
};

📚 입력 매개변수의 값을 수정할 때

다음의 코드를 보자.

const discount = (inputValue, quantity) => {
  if (inputValue > 50) {
    inputValue = inputValue - 2;
  }
  if (quantity > 100) {
    inputValue = inputValue - 1;
  }
  return inputValue;
};

inputValue는 함수에 데이터를 전달하는 용도와 결과를 호출자에 반환하는 용도로 쓰였다. inputValue 변수를 쪼개보자.

첫 번째 if문에서 inputValue와 비교하도록 수정한다. 입력 값에 기초해, 결과값을 누적해 계산한다는 사실을 명확히 드러내도록 한다.

const discount = (inputValue, quantity) => {
  let result = inputValue; // result 선언 및 inputValue 대입
  if (inputValue > 50) { // inputValue와 비교
    result = result - 2;
  }
  if (quantity > 100) {
    result = result - 1;
  }
  return result;
};

💻 변수 쪼개기 코드


필드 이름 바꾸기

example-code-2

이름은 프로그램을 이해하는 데 큰 역할을 한다.

📜 절차

  1. 레코드의 유효 범위가 제한적이라면 필드에 접근하려는 모든 코드를 수정한다. 이후 단계는 필요없다.
  2. 레코드가 캡슐화되지 않았다면 우선 레코드를 캡슐화한다.
  3. 캡슐화된 객체 안의 private 필드명을 변경하고 그에 맞게 내부 메서드들을 수정한다.
  4. 생성자의 매개변수 중 필드와 이름이 겹치는게 있다면 함수 선언 바꾸기로 변경한다.
  5. 접근자들의 이름도 바꿔준다.

💻 필드 이름 바꾸기 코드


파생 변수를 질의 함수로 바꾸기

example-code-3

가변 데이터의 유효 범위는 가능한 한 좁혀야 한다.

📜 절차

  1. 변수 값이 갱신되는 지점을 찾는다. 필요하면 변수 쪼개기를 활용해 각 갱신 지점에서 변수를 분리한다.
  2. 해당 변수의 값을 계산하는 함수를 만든다.
  3. 변수를 읽는 코드를 모두 함수 호출로 대체한다.
  4. 변수를 선언하고 갱신하는 코드를 죽은 코드 제거하기로 없앤다.

다음의 코드를 보자.

class ProductionPlan {
  get production() {
    return this._production;
  }
  applyAdjustment(anAdjustment) {
    this._adjustments.push(anAdjustment);
    this._production += anAdjustment.amount;
  }
}

adjustment 값을 적용하는 과정에서, 관련없는 production 값까지 갱신했다. 이 클래스에서 발생한 데이터 중복을 제거해보자.

production 값을 계산하는 함수를 만든다.

class ProductionPlan {
  // ...
  get calculatedProduction() {
    return this._adjustments.reduce((sum, a) => sum + a.amount, 0);
  }
}

그런 다음 calculatedProduction 함수를 인라인하고, 옛 변수를 참조하는 모든 코드를 정리한다.

class ProductionPlan {
  get production() {
    return this._adjustments.reduce((sum, a) => sum + a.amount, 0);
  }
  applyAdjustment(anAdjustment) {
    this._adjustments.push(anAdjustment);
  }
}

💻 파생 변수를 질의 함수로 바꾸기 코드


참조를 값으로 바꾸기

example-code-4

객체를 다른 객체에 중첩하면 내부 객체를 참조 혹은 값으로 취급할 수 있는데,
값으로 다루는 경우에는 새로운 속성을 담은 객체로 기존 내부 객체를 통째로 대체한다.
이런 값 객체는 불변이기 때문에, 다른 곳에서 값을 변경하지 않을까 걱정할 필요가 없다.

📜 절차

  1. 후보 클래스가 불변인지, 혹은 불변이 될 수 있는지 확인한다.
  2. 각각의 세터를 하나씩 제거한다.
  3. 이 값 객체의 필드들을 사용하는 동치성 비교 메서드를 만든다.

💻 참조를 값으로 바꾸기 코드


값을 참조로 바꾸기

example-code-5

데이터의 일관성이 필요한 경우에는 값을 참조로 바꾸도록 한다.


💻 값을 참조로 바꾸기 코드


매직 리터럴 바꾸기

example-code-6

의미를 알기 어려운 리터럴이 보이면 명확하게 바꿔주도록 한다.

매직 리터럴(magic literal)이란 의미를 알 수 없는 리터럴 값을 말한다. aValue === 'M' 이라는 코드가 있을 때, M이 매직 리터럴이다.

매직 리터럴 바꾸기 기법을 이용하면 다음과 같이 리팩터링할 수 있다.

  • 해당 값이 쓰이는 모든 곳을 적절한 이름의 상수로 바꾸기: aValue === MALE_GENDER
  • 해당 값이 쓰이는 모든 곳을 함수 호출로 바꾸기: isMale(aValue)

0개의 댓글