😀 책에서 기억하고 싶은 내용을 써보세요.

리팩터링 전

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;

 

 

 

배경

  • 함수 안에서 어떤 코드의 결괏값을 뒤에서 다시 참조할 목적으로 임시 변수를 쓰기도 한다.
  • 임시 변수를 사용하면 값을 계산하는 코드가 반복되는 걸 줄이고 (변수 이름을 통해) 값의 의미를 설명할 수도 있어서 유용하다.

 

그런데 한 걸음 더 나아가 아예 함수로 만들어 사용하는 편이 나을 때가 많다.

  • 긴 함수의 한 부분을 함수로 추출하고자 할 때 먼저 변수들을 각각의 함수로 만들면 일이 수월해진다.
  • ⇒ 추출한 함수에 변수를 따로 전달할 필요가 없어지기 때문
  • 추출한 함수와 원래 함수의 경계가 더 분명해진다.
  • ⇒ 부자연스러운 의존 관계나 부수효과를 찾고 제거하는 데 도움이 된다.
  • 변수 대신 함수로 만들어두면 비슷한 계산을 수행하는 다른 함수에서도 사용할 수 있어 코드 중복이 줄어든다.
  • ⇒ 여러 곳에서 똑같은 방식으로 계산되는 변수를 발견할 때마다 함수로 바꿀 수 있는지 확인

 

이번 리팩터링은 클래스 안에서 적용할 때 효과가 가장 크다.

  • 클래스는 추출할 메서드들에 공유 컨텍스트를 제공하기 때문
  • 클래스 바깥의 최상위 함수로 추출하면 매개변수가 너무 많아져서 함수를 사용하는 장점이 줄어든다.
  • 중첩 함수를 사용하면 이런 문제는 없지만 관련 함수들과 로직을 널리 공유하는 데 한계가 있다.

 

임시 변수를 질의 함수로 바꾼다고 다 좋아지는건 아니다.

  • 자고로 변수는 값을 한 번만 계산하고, 그 뒤로는 읽기만 해야 한다.
  • 가장 단순한 예로, 변수에 값을 한 번 대입한 뒤 더 복잡한 코드 덩어리에서 여러 차례 다시 대입하는 경우는 모두 질의 함수로 추출해야 한다.
  • 계산 로직은 변수가 다음번에 사용될 때 수행해도 똑같은 결과를 내야 한다. ⇒ 그래서 ‘옛날 주소’ 처럼 스냅숏 용도로 쓰이는 변수에는 이 리팩터링을 적용하면 안 된다.

 

절차

  1. 변수가 사용되기 전에 값이 확실히 결정되는지, 변수를 사용할 때마다 계산 로직이 매번 다른 결과를 내지는 않는지 확인한다.
  2. 읽기 전용으로 만들 수 있는 변수는 읽기 전용으로 만든다.
  3. 테스트한다.
  4. 변수 대입문을 함수로 추출한다.
    • 변수와 함수가 같은 이름을 가질 수 없다면 함수 이름을 임시로 짓는다. 또한, 추출한 함수가 부수효과를 일으키지는 않는지 확인한다. 부수효과가 있다면 질의 함수와 변경 함수 분리하기로 대처한다.
  5. 테스트한다.
  6. 변수 인라인하기로 임시 변수를 제거한다.

 

예시

간단한 주문 클래스

// 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 접근자 메서드를 단순히 해당 값을 읽어오는 것으로 사용하는 게 아니라, 똑같은 방식으로 계산되어 같은 값이 나오는 것들을 묶어 줄 수 있다는 것을 알게 되었습니다(캡슐화). ⇒ 이 방법을 통해 유저 혹은 클라이언트는 내부 구조를 알 필요 없이 해당 함수를 가져다 사용할 수 있습니다. (유저의 편의성 증대)

 

 

🔎 궁금한 내용이 있거나, 잘 이해되지 않는 내용이 있다면 적어보세요.

  • 캡슐화와 추상화의 차이는?
    • 캡슐화와 추상화는 개발 비용을 낮춰주는 객체지향의 두 가지 특징이다.
    • 캡슐화는 기능 구현을 외부로부터 감추고, 내부의 구현 병경이 외부로 전파되는 것을 막아줍니다.
    • 추상화는 의존 대상을 추상 타입으로 간접 참조하고, 사용하고 있는 의존 대상의 변경이 사용하는 입장에는 영향을 주지 않습니다.
    • 둘은 상호 보완적인 개념입니다. 추상화: 객체의 동작, 기능 자체에 중점을 둔다. 캡슐화: 객체 내부 상태에 대한 정보를 숨기는 방식으로 이루어진다.

 

 

 

반응형

+ Recent posts