class Order {
constructor(data) {
this._priority = data.priority;
}
// 1, 2
get priority() { return this._priority; }
get priorityString() { return this._priority.toString(); } // 6
set priority(aString) { this._priority = new Priority(aString); } // 4
}
class Priority {
constructor(value) {
if (value instanceof Priority) { return value; }
// 우선순위 값 검증 및 비교 로직
if (Priority.legalValues().includes(value)) {
this._value = value;
} else {
throw new Error(`<${value}}> is invalid for Priority`);
}
}
static legalValues() { return ['low', 'normal', 'high', 'rush']; }
get _index() { return Priority.legalValues().findIndex((s) => s === this._value); }
toString() { return this._value; }
equals(other) { return this._index === other._index; }
higherThan(other) { return this._index > other._index; }
lowerThan(other) { return this._index < other._index; }
}
임시 변수를 질의 함수로 바꾸기
다른 함수에서도 사용할 수 있어 코드 중복을 줄일 수 있다.
여러 곳에서 똑같은 방식으로 계산되는 변수를 발견하면 질의 함수로 바꿀 수 있을지 살펴보자.
값이 대입된 변수가 있는데, 복잡한 로직에서 여러 차례 다시 대입되는 경우 모두 질의 함수로 추출하자.
개요
Before
class Order {
constructor(quantity, item) {
this._quantity = quantity;
this._item = item;
}
get price() { //
var basePrice = this._quantity * this._itemPrice;
var discountFactor = 0.98;
if (basePrice > 1000) {
discountFactor -= 0.03;
}
return basePrice * discountFactor;
}
}
After
class Order {
constructor(quantity, item) {
this._quantity = quantity;
this._item = item;
}
get basePrice() { return this._quantity * this._itemPrice; }
get discountFactor() {
var discountFactor = 0.98;
if (basePrice > 1000) {
discountFactor -= 0.03;
}
return discountFactor;
}
get price() { return this.basePrice * this.discountFactor; } //
}
절차
변수를 사용할 때마다 매번 다른 결과를 갖는지 확인
변수를 읽기 전용으로 만들 수 있다면 읽기 전용으로 만들기
테스트
재대입 코드가 있는지 발견할 수 있다.
변수 대입문을 함수로 추출
사이드 이펙트가 있다면 질의 함수와 변경 함수로 분리하기로 대처
테스트
변수 인라인하기로 임시 변수 제거
클래스 추출하기
반대 리팩터링 : 클래스 인라인하기
클래스는 명확하게 추상화하고 소수의 주어진 역할만 처리하자.
따로 묶을 수 있는 데이터와 메서드가 보인다면 어서 분리하자.
개요
Before
class Person {
get officeAreaCode() { return this._officeAreaCode; }
get officeNumber() { return this._officeNumber; }
}
After
class Person {
get officeAreaCode() { return this._telephoneNumber.areaCode; }
get officeNumber() { return this._telephoneNumber.number; }
}
class TelephoneNumber {
get areaCode() { return this._areaCode; }
get number() { return this._number; }
}
절차
클래스 역할 분리 방법 정하기
분리될 역할을 담당할 클래스 만들기
원래 클래스 생성자에서 새로운 클래스의 인스턴스 생성
분리된 역할에 필요한 필드들을 새로운 클래스로 옮기기
메서드들도 새로운 클래스로 옮기기(함수 옮기기)
호출을 당하는 일이 많은 메서드부터 옮기자
양쪽 클래스의 인터페이스를 살피며 불필요 메서드 제거 및 환경에 맞게 이름 수정(함수 선언 바꾸기)
새로운 클래스를 외부로 노출할지 결정
외부로 노출할 경우 "새로운 클래스에 참조를 값으로 바꾸기" 적용 고민
Example
class Person {
constructor() {
this._telephoneNumber = new TelephoneNumber();
}
get officeAreaCode() { return this._telephoneNumber.areaCode; }
set officeAreaCode(arg) { this._telephoneNumber.areaCode = arg; }
get officeNumber() { return this._telephoneNumber.number; }
set officeNumber(arg) { this._telephoneNumber.number = arg; }
get telephoneNumber() { return this._telephoneNumber.toString(); }
}
class TelephoneNumber {
get areaCode() { return this.areaCode; }
set areaCode(arg) { this.areaCode = arg; }
get number() { return this.number; }
set number(arg) { this.number = arg; }
get toString() { return `(${this.areaCode}) ${this.number}`; }
}
클래스 인라인하기
역할 옮기기 리팩터링 후 더 이상 제 역할을 못 하는 클래스는, 자신을 가장 많이 사용하는 클래스로 흡수시키자.
반대 리팩터링 : 클래스 추출하기
개요
Before
class Person {
get officeAreaCode() { return this._telephoneNumber.areaCode; }
get officeNumber() { return this._telephoneNumber.number; }
}
class TelephoneNumber {
get areaCode() { return this._areaCode; }
get number() { return this._number; }
}
After
class Person {
get officeAreaCode() { return this._officeAreaCode; }
get officeNumber() { return this._officeNumber; }
}
절차
소스 클래스의 각 public 메서드에 대응하는 메서드들을 타깃 클래스에 생성
소스 클래스의 메서드를 사용하는 코드를 모두 타깃 클래스의 위임 메서드를 사용하도록 수정 (수정마다 테스트)
소스 클래스의 메서드와 필드를 모두 타깃 클래스로 옮기기 (이동마다 테스트)
소스 클래스를 삭제
위임 숨기기
캡슐화는 모듈들이 시스템의 다른 부분에 대해 알아야 할 내용을 줄여준다.
인터페이스와의 의존성을 없애려면 위임 메서드를 만들어서 위임 객체의 존재를 숨기자.
반대 리팩터링 : 중개자 제거하기
개요
Before
manager = aPerson.department.manager;
After
manager = aPerson.manager;
class Person {
get manager() { return this.department.manager; }
}
절차
위임 객체의 각 메서드에 해당하는 위임 메서드를 서버에 생성
클라이언트가 서버를 호출하도록 수정
서버로부터 위임 객체를 얻는 접근자 제거
테스트
example
manager = aPerson.manager;
class Person {
constructor(name) {
this._name = name;
}
get name() { return this._name; }
get manager() { return this._department.manager; } // 부서 클래스를 숨기고 위임 메서드 생성
set department(arg) { this._department = arg; }
}
class Department {
get chargeCode() { return this._chargeCode; }
set chargeCode(arg) { this._chargeCode = arg; }
get manager() { return this._manager; }
set manager(arg) { this._manager = arg; }
}
중개자 제거하기
중개자 역할로 전략하여 단순히 전달만 하는 클래스(위임 메서드들로 쌓인)는 차라리 위임 객체를 직접 호출하게 하자.
위임 숨기기나 중개자 제거하기를 적당히 섞어 상황에 맞게 처리하자.
반대 리팩터링 : 위임 숨기기
개요
Before
manager = aPerson.manager;
class Person {
get manager() { return this.department.manager; }
}
After
manager = aPerson.department.manager;
절차
위임 객체를 얻는 Getter 생성
위임 메서드를 호출하는 코드를 Getter 로 수정
위임 메서드 삭제
알고리즘 교체하기
더 간명한 방법을 찾으면 복잡한 기존 코드를 간명한 방식으로 고치자.
알고리즘 교체를 위해 반드시 메서드를 가능한 잘게 나누자.
개요
Before
function foundPerson(people) {
for (let i = 0; i < people.length; i++) {
if (people[i] === 'Don') {
return 'Don';
}
if (people[i] === 'John') {
return 'John';
}
if (people[i] === 'Kent') {
return 'Kent';
}
}
return "";
}