Refactoring
Chapter 9. 데이터 조직화
- 데이터 구조는 프로그램에서 중요한 역할을 수행한다.
변수 쪼개기
역할이 둘 이상인 변수는 쪼개자.
여러 용도로 쓰인 변수는 코드를 읽는 과정에서 커다란 혼란을 주게 된다.
개요
Before
let temp = 2 * (height + width);
console.log(temp);
temp = height + width;
console.log(temp);
After
const perimeter = 2 * (height + width);
console.log(perimeter);
const area = height * width;
console.log(area);
절차
- 변수를 선언한 곳과 값을 처음 대입하는 곳에서
변수 이름을 바꾸기
- 총합 계산, 문자열 연결, 스트림 쓰기 등에 흔히 사용되는 수집변수는 제외
- 가능하면
불변으로 선언
- 이 변수에 두 번째로 값을 대입하는 곳 앞까지의 모든 참조를
새로운 변수 이름
으로 수정 - 두 번째 대입 시 변수를 원래 이름으로 다시 선언
- 테스트
- 반복
Example
function distanceTravelled(scenario, time) {
let result;
// 첫 번째 힘이 유발한 초기 가속도
const primaryAcceleration = scenario.primaryForce / scenario.mass; // 1, 2
let primaryTime = Math.min(time, scenario.delay);
result = 0.5 * primaryAcceleration * primaryTime * primaryTime; // 3
let secondaryTime = time - scenario.delay;
if (secondaryTime > 0) {
let primaryVelocity = primaryAcceleration * scenario.delay; // 3
// 두 번째 힘까지 반영된 후의 가속도
const secondaryAcceleration = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass; // 4 -> 6 (1, 2)
result += primaryVelocity * secondaryTime + 0.5 * secondaryAcceleration * secondaryTime * secondaryTime; // 3
}
return result;
}
필드 이름 바꾸기
데이터 구조는 프로그램을 이해하는 데 큰 역할을 한다.
데이터는 무슨 일이 벌어지는지 이해하는 열쇠다.
개요
Before
class Organization {
get name() {}
}
After
class Organization {
get title() {}
}
절차
- 레코드의 유효 범위가 제한적이라면 필드에 접근하는 모든 코드를 수정 후 테스트
- 유효 범위가 제한적인 레코드는 절차 끝
- 레코드가 캡슐화되지 않았다면
레코드 캡슐화
- 캡슐화된 객체 안의 private
필드명 변경 후
그에 맞게내부 메서드 수정
- 테스트
- 생성자의 매개변수 중 필드와 이름이 겹치는 게 있다면
함수 선언 바꾸기
로 변경 - 접근자 이름 변경
Example
class Organization { // 2. 캡슐화
constructor(data) {
this._title = data.title; // 3. 필드명 변경 후 메서드 수정
this._country = data._country;
}
get title() { return this._title; } // 3.
set title(aString) { this._title = aString; } // 3.
get country() { return this._country; }
set country(aCountryCode) { this._country = aCountryCode; }
}
const organization = new Organization({ // 6. 접근자 이름 변경
title: '애크미 구스베리',
country: 'GB',
});
파생 변수를 질의 함수로 바꾸기
가변 데이터의 유효 범위를 가능한 좁혀야 한다.
값을 쉽게 계산해낼 수 있는 변수들을 모두 제거하자.
- 새로운 데이터 구조를 생성하는 변형 연산은 예외다.
개요
Before
get discountedTotal() { return this._discountedTotal; }
set discountedTotal(aNumber) {
const old = this._discount;
this._discount = aNumber;
this._discountTotal += old - aNumber;
}
After
get discountedTotal() { return this._baseTotal - this._discount; }
set discountedTotal(aNumber) { this._discount = aNumber; }
절차
- 변수 값이 갱신되는 지점 찾기
- 필요 시
변수 쪼개기
로 각 갱신 지점에서 변수 분리하기
- 필요 시
- 해당 변수의 값을 계산해주는
함수 만들기
- 함수의 계산 결과가 변수의 값과 같은지 테스트
- 변수를 읽는 코드를 모두 함수 호출로 수정 후 테스트
- 변수를 선언하고 갱신하는 코드에
죽은 코드 제거하기
적용
Example
class ProductionPlan {
constructor(production) {
this._initialProduction = production; // 1. 변수 쪼개기
this._productionAccumulator = 0; // 1.
this._adjustments = [];
}
get production() { return this._initialProduction + this._productionAccumulator; } // 2. 변수 값 계산
get calculatedProductionAccumulator() { return this._adjustments.reduce((sum, a) => sum + a.amount, 0); }
applyAdjustment(anAdjustment) { this._adjustments.push(anAdjustment); }
}
참조를 값으로 바꾸기
참조로 다루는 경우는 내부 객체를 그대로 둔 채 객체의 속성만 갱신
값으로 다루는 경우 새로운 속성을 담은 객체로 기존 내부 객체를 대체
값 객체는 불변이기 때문에 자유롭게 활용하기 좋고, 분산 시스템과 동시성 시스템에서 유용하다.
단, 객체를 공유하고자 한다면 공유 객체를 참조로 다뤄야 한다.
반대 리팩터링 : 값을 참조로 바꾸기
개요
Before
class Product {
applyDiscount(arg) {
this._price.amount -= arg;
}
}
After
class Product {
applyDiscount(arg) {
this._price = new Money(this._price.amount - arg, this._price.currency);
}
}
절차
- 후보 클래스가 불변인지 확인하기 (
불변으로 만들기
) - 필드들의
세터 제거
하기 - 값 객체의 필드들을 사용하는
동치성 비교 메서드 만들기
- JAVA 에서는 Object.equals(), Object.hashCode() method override.
Example
/** Person ********************************************/
class Person {
constructor() {
this._telephoneNumber = new TelephoneNumber();
}
get officeAreaCode() { return this._telephoneNumber.areaCode; }
set officeAreaCode(arg) { this._telephoneNumber = new TelephoneNumber(arg, this.officeNumber); } // 1.
get officeNumber() { return this._telephoneNumber.number; }
set officeNumber(arg) { this._telephoneNumber = new TelephoneNumber(this.officeNumber, arg);} // 1.
}
/** TelephoneNumber ********************************************/
class TelephoneNumber {
constructor(areaCode, number) { // 1. 불변으로 만들기
this._areaCode = areaCode;
this._number = number;
}
equals(other) { // 3. 동치성 비교
if (!(other instanceof TelephoneNumber)) { return false; }
return this.areaCode === other.areaCode && this.number === other.number;
}
get areaCode() { return this.areaCode; }
get number() { return this.number; }
// 2. 필드 세터 제거
}
값을 참조로 바꾸기
같은 데이터를 물리적으로 복제해 사용할 때 데이터를 갱신해야 한다면 값을 참조로 바꾸자.
- 반대 리팩터링 : 참조를 값으로 바꾸기
개요
Before
let customer = new customer(customerData);
After
let customer = customerRepository.get(customer.id);
절차
- 같은 부류에 속하는 객체들을 보관할
저장소(객체) 만들기
- 생성자에서 특정 객체를 정확히 찾아낼 방법이 있는지 확인하기
- 호스트 객체의 생성자들을 수정하여 필요한 객체를 이 저장소에서 찾도록 하기
- 테스트
매직 리터럴 바꾸기
매직 리터럴 : 소스 코드의 여러 곳에 등장하는 일반적인 리터럴 값
숫자 대신 상수를 사용하여 코드 자체가 뜻을 분명하게 드러내도록 해보자.
- 상수가 특별한 비교 로직에 주로 쓰인다면 함수 호출로 바꾸는 쪽을 선호.
- aValue === “M” 대신 isMali(aValue)
- 상수를 과용하지는 말자.
- const ONE = 1
개요
Before
function potentialEnergy(mass, height) {
return mass * 9.81 * height;
}
After
const STANDARD_GRAVITY = 9.81;
function potentialEnergy(mass, height) {
return mass * STANDARD_GRAVITY * height;
}
절차
- 상수를 선언하고 매직 리터럴 대입
- 해당 리터럴이 사용되는 곳 찾기
- 리터럴을 상수로 대체한 후 테스트