냄새 9. 기능 편애
- etc/리팩토링
- 2023. 5. 10.
들어가기 전
이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.
냄새 9. 기능 편애(Feature Envy)
- 어떤 모듈에 있는 함수가 다른 모듈에 있는 데이터나 함수를 더 많이 참조하는 경우에 발생한다.
- 이것은 특정 함수가 기능을 너무 욕심내서, 특정 함수에 많은 기능이 있는 것이다.
- 예) 다른 객체의 getter를 여러 개 사용하는 메서드
- 관련 리팩토링 기술
- '함수 옮기기'를 사용해서 함수를 적절한 위치로 옮긴다. (참조하는 데이터/메서드가 많은 클래스 쪽)
- 함수 일부분만 다른 곳의 데이터와 함수를 많이 참조한다면 '함수 추출하기'로 함수를 나눈 다음에 함수를 옮길 수 있다.
- 만약에 여러 모듈(클래스)를 참조하고 있다면
- 가장 많은 데이터를 참조하는 곳으로 옮김.
- 함수를 여러 개로 쪼개서, 각 클래스로 분산
- 데이터와 해당 데이터(필드)를 참조하는 행동(메서드)을 같은 곳(클래스)에 두도록 하자.
- 이 기준의 예외는 데이터와 행동을 분리한 디자인 패턴 (전략 패턴, 방문자 패턴)을 적용할 수도 있다.
어떤 함수가 욕심을 내다보면, 다른 모듈이 가지고 있어야 할 기능 조차 특정 함수가 가지고 있는 경우가 있다. 이런 함수의 특징은 다른 클래스의 변수 / 함수를 많이 참조하는 경우다. 이 함수는 아마도 적절한 위치에 있는 것이 아닐 수 있다. 혹은 함수의 특정 부분만 다른 클래스를 많이 참조한다면, 그 부분을 함수로 추출한 후, 다른 클래스로 이동시켜주는 것이 좋을 수 있다.
기본적인 처리 방법
만약에 어떤 함수가 다른 여러 모듈(클래스)를 참조한다면, 두 가지 방법으로 처리할 수 있다.
- 함수를 통째로 가장 많이 참조하는 클래스로 옮김.
- 함수를 여러 개로 나눈 다음, 각 함수를 관련된 클래스로 옮김.
옮기는 기준은 참조하는 함수 / 데이터가 된다. 모듈화를 할 때 가장 기본적인 룰은 관련있는 데이터와 행동(함수)는 같은 곳에 두는 것이다. 그런데 항상 적용되지는 않는데, 행동이 여러 조건에 따라 바뀌어야 한다면 행동만을 데이터로부터 따로 분리할 수 있다. (Strategy, Visitor 패턴는 예외 경우임.)
코드
아래 코드를 살펴보면 Bill 클래스가 기능에 욕심을 내고 있다.
- 참조 관점에서 봤을 때, Bill 클래스는 calculateBill() 안에서 자신의 필드 / 메서드를 아무것도 참조하지 않고 다른 클래스의 메서드만 참조하고 있다. 따라서 이 관점에서 calculateBill()은 기능 편애의 냄새를 풍긴다.
- 문맥 관점에서도 계산지 클래스에서 전기세 / 가스세를 직접 구하고 있다. 전기세 / 가스세는 전기 사용량 / 가스 사용량 클래스에서 직접 구하는 것이 더욱 문맥적으로 맞을 수 있다.
따라서 해당 클래스는 기능 편애의 냄새가 나기 때문에 리팩토링이 필요하다. 리팩토링은 다음과 같이 진행한다.
- 각 계산하는 메서드는 각기 다른 클래스에 있어야 한다. 일차적으로 Bill 클래스에서 메서드로 추출한다.
- 추출한 메서드를 각 클래스로 옮긴다.
- 문맥을 점검한다.
public class Bill {
private ElectricityUsage electricityUsage;
private GasUsage gasUsage;
// Bill 클래스가 electiryUsage / GasUsage의 메서드를 많이 참조한다. (심지어 자기것은 없음)
// 기능 편애의 냄새가 난다.
public double calculateBill() {
var electicityBill = electricityUsage.getAmount() * electricityUsage.getPricePerUnit();
var gasBill = gasUsage.getAmount() * gasUsage.getPricePerUnit();
return electicityBill + gasBill;
}
}
리팩토링의 중간 단계는 다음과 같다. 코드를 그대로 옮기기보다는 관련된 부분만 '함수로 추출하기'를 한 후에, 해당 함수를 클래스로 옮겨주는 것이다.
public class Bill {
private ElectricityUsage electricityUsage;
private GasUsage gasUsage;
// Bill 클래스가 electiryUsage / GasUsage의 메서드를 많이 참조한다. (심지어 자기것은 없음)
// 기능 편애의 냄새가 난다.
// 각각을 메서드로 추출한 후, 해당하는 클래스로 옮긴다.
public double calculateBill() {
double electicityBill = getElecticityBill();
double gasBill = getGasBill();
return electicityBill + gasBill;
}
private double getGasBill() {
var gasBill = gasUsage.getAmount() * gasUsage.getPricePerUnit();
return gasBill;
}
private double getElecticityBill() {
var electicityBill = electricityUsage.getAmount() * electricityUsage.getPricePerUnit();
return electicityBill;
}
}
최종 수정
최종 수정의 결과로 다음과 같이 코드가 변경된다.
public class Bill {
private ElectricityUsage electricityUsage;
private GasUsage gasUsage;
public double calculateBill() {
return electricityUsage.getElecticityBill() + gasUsage.getGasBill();
}
}
public class ElectricityUsage {
private double amount;
private double pricePerUnit;
public ElectricityUsage(double amount, double pricePerUnit) {
this.amount = amount;
this.pricePerUnit = pricePerUnit;
}
public double getElecticityBill() {
return this.getAmount() * this.getPricePerUnit();
}
public double getAmount() {
return amount;
}
public double getPricePerUnit() {
return pricePerUnit;
}
}
public class GasUsage {
private double amount;
private double pricePerUnit;
public GasUsage(double amount, double pricePerUnit) {
this.amount = amount;
this.pricePerUnit = pricePerUnit;
}
public double getGasBill() {
return this.getAmount() * this.getPricePerUnit();
}
public double getAmount() {
return amount;
}
public double getPricePerUnit() {
return pricePerUnit;
}
}
'etc > 리팩토링' 카테고리의 다른 글
냄새 11. 기본형 집착 (0) | 2023.05.10 |
---|---|
냄새 10. 데이터 뭉치 (Data Clumps) (0) | 2023.05.10 |
리팩토링 29. 클래스 인라인 (0) | 2023.05.10 |
리팩토링 28. 함수 인라인 (0) | 2023.05.10 |
리팩토링 27. 필드 옮기기 (0) | 2023.05.10 |