😀 책에서 기억하고 싶은 내용을 써보세요.
리팩터링 전
const basePrice = this._quantity * this._itemPrice;
if (basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
리팩터링 후
get basePrice() {this._quantity * this._itemPrice;}
...
if (this.basePrice > 1000)
return this.basePrice * 0.95;
else
return this.basePrice * 0.98;
배경
- 함수 안에서 어떤 코드의 결괏값을 뒤에서 다시 참조할 목적으로 임시 변수를 쓰기도 한다.
- 임시 변수를 사용하면 값을 계산하는 코드가 반복되는 걸 줄이고 (변수 이름을 통해) 값의 의미를 설명할 수도 있어서 유용하다.
그런데 한 걸음 더 나아가 아예 함수로 만들어 사용하는 편이 나을 때가 많다.
- 긴 함수의 한 부분을 함수로 추출하고자 할 때 먼저 변수들을 각각의 함수로 만들면 일이 수월해진다.
- ⇒ 추출한 함수에 변수를 따로 전달할 필요가 없어지기 때문
- 추출한 함수와 원래 함수의 경계가 더 분명해진다.
- ⇒ 부자연스러운 의존 관계나 부수효과를 찾고 제거하는 데 도움이 된다.
- 변수 대신 함수로 만들어두면 비슷한 계산을 수행하는 다른 함수에서도 사용할 수 있어 코드 중복이 줄어든다.
- ⇒ 여러 곳에서 똑같은 방식으로 계산되는 변수를 발견할 때마다 함수로 바꿀 수 있는지 확인
이번 리팩터링은 클래스 안에서 적용할 때 효과가 가장 크다.
- 클래스는 추출할 메서드들에 공유 컨텍스트를 제공하기 때문
- 클래스 바깥의 최상위 함수로 추출하면 매개변수가 너무 많아져서 함수를 사용하는 장점이 줄어든다.
- 중첩 함수를 사용하면 이런 문제는 없지만 관련 함수들과 로직을 널리 공유하는 데 한계가 있다.
임시 변수를 질의 함수로 바꾼다고 다 좋아지는건 아니다.
- 자고로 변수는 값을 한 번만 계산하고, 그 뒤로는 읽기만 해야 한다.
- 가장 단순한 예로, 변수에 값을 한 번 대입한 뒤 더 복잡한 코드 덩어리에서 여러 차례 다시 대입하는 경우는 모두 질의 함수로 추출해야 한다.
- 계산 로직은 변수가 다음번에 사용될 때 수행해도 똑같은 결과를 내야 한다. ⇒ 그래서 ‘옛날 주소’ 처럼 스냅숏 용도로 쓰이는 변수에는 이 리팩터링을 적용하면 안 된다.
절차
- 변수가 사용되기 전에 값이 확실히 결정되는지, 변수를 사용할 때마다 계산 로직이 매번 다른 결과를 내지는 않는지 확인한다.
- 읽기 전용으로 만들 수 있는 변수는 읽기 전용으로 만든다.
- 테스트한다.
- 변수 대입문을 함수로 추출한다.
- 변수와 함수가 같은 이름을 가질 수 없다면 함수 이름을 임시로 짓는다. 또한, 추출한 함수가 부수효과를 일으키지는 않는지 확인한다. 부수효과가 있다면 질의 함수와 변경 함수 분리하기로 대처한다.
- 테스트한다.
- 변수 인라인하기로 임시 변수를 제거한다.
예시
간단한 주문 클래스
// Order 클래스
constructor(quantity, item) {
this._quantity = quantity;
this._item = item;
}
get price() {
var basePrice = this._quantity * this._item.price;
var discountFactor = 0.98;
if (basePrcie > 1000) discountFactor -= 0.03;
return basePrice * discountFactor;
}
여기서 임시 변수인 basePrice와 discountFactor를 메서드로 바꿔보자.
2️⃣ 먼저 basePrice에 const를 붙여 읽기 전용으로 만들고 3️⃣ 테스트해본다.
- 못 보고 지나친 재대입 코드를 찾을 수 있다.
- ⇒ 컴파일 에러 발생
// Order 클래스
constructor(quantity, item) {
this._quantity = quantity;
this._item = item;
}
get price() {
const basePrice = this._quantity * this._item.price;
var discountFactor = 0.98;
if (basePrice > 1000) discountFactor -= 0.03;
return basePrice * discountFacotr;
}
4️⃣ 그런 다음 대입문의 우변을 게터로 추출한다.
// Order 클래스...
const basePrice = this.basePrice;
var discountFactor = 0.98;
if (basePrice > 1000) discountFacotr -= 0.03;
return basePrice * discountFactor;
}
get basePrice() {
return this._quantity * this._item.price;
}
5️⃣ 테스트한 다음 6️⃣ 변수를 인라인 한다.
// Order 클래스...
get price() {
var discountFactor = 0.98;
if (this.basePrice > 1000) discountFactor -= 0.03;
return this.basePrice * discountFactor;
}
discountFactor 변수도 같은 순서로 처리한다. 4️⃣ 먼저 함수 추출하기다.
// Order 클래스...
get price() {
const discountFactor = this.discountFactor;
return this.basePrice * discountFactor;
}
get discountFactor() {
var discountFactor = 0.98;
if (this.basePrice > 1000) discountFactor -= 0.03;
return discountFactor;
}
이번에는 discountFactor에 값을 대입하는 문장이 둘인데, 모두 추출한 함수에 넣어야 한다.
2️⃣ 원본 변수는 마찬가지로 const로 만든다.
6️⃣ 마지막으로 변수 인라인 차례다.
// Order 클래스...
get price() {
return this.basePrice * this.discountFactor;
}
🤔 오늘 읽은 소감은? 떠오르는 생각을 가볍게 적어보세요.
- getter 접근자 메서드를 단순히 해당 값을 읽어오는 것으로 사용하는 게 아니라, 똑같은 방식으로 계산되어 같은 값이 나오는 것들을 묶어 줄 수 있다는 것을 알게 되었습니다(캡슐화). ⇒ 이 방법을 통해 유저 혹은 클라이언트는 내부 구조를 알 필요 없이 해당 함수를 가져다 사용할 수 있습니다. (유저의 편의성 증대)
🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.
- 캡슐화와 추상화의 차이는?
- 캡슐화와 추상화는 개발 비용을 낮춰주는 객체지향의 두 가지 특징이다.
- 캡슐화는 기능 구현을 외부로부터 감추고, 내부의 구현 병경이 외부로 전파되는 것을 막아줍니다.
- 추상화는 의존 대상을 추상 타입으로 간접 참조하고, 사용하고 있는 의존 대상의 변경이 사용하는 입장에는 영향을 주지 않습니다.
- 둘은 상호 보완적인 개념입니다. 추상화: 객체의 동작, 기능 자체에 중점을 둔다. 캡슐화: 객체 내부 상태에 대한 정보를 숨기는 방식으로 이루어진다.
반응형