| 인프런 - 백기선님의 코딩으로 학습하는 리팩토링 강의를 수강하며 정리한 글입니다.
산탄총 수술
- 어떠한 변화가 발생하였을 때 여러 모듈, 함수, 클래스를 변경해야 하는 상황
- 변경 사항이 여러 곳에 흩어진다면 찾아서 수정하기 어렵고, 중요한 변경 사항을 놓칠 수 있다.
- 관련 리팩토링 기술
- 함수 옮기기 또는 필드 옮기기 -> 필요한 변경 내역을 하나의 클래스로 모을 수 있다.
- 여러 함수를 클래스로 묶기 -> 비슷한 데이터를 사용하는 여러 함수를 하나의 클로스로 모을 수 있다.
- 단계 쪼개기 -> 공통으로 사용되는 함수의 결과물들을 하나로 묶을 수 있다.
- 함수 인라인, 클래스 인라인 -> 흩어진 로직을 하나로 묶을 수 있다.
Refactoring 1. 필드 옮기기
- 좋은 데이터 구조를 가지고 있다면, 해당 데이터에 기반한 어떤 행위를 코드(메서드)로 옮기는 것은 간편하고 쉬워진다.
- 처음에는 타당해 보이는 구조여도, 도메인이 추가되거나 수정하는 과정에서 틀린 의사 결정이 될 수 있다.
- 근거
- 어떤 데이터를 항상 어떤 레코드와 함께 전달할 경우
- 어떤 레코드를 변경할 때 다른 레코드의 필드도 수정해야 할 경우
- 여러 레코드에 동일한 필드를 수정해야 할 경우
Before
public class Customer {
private String name;
private double discountRate;
private CustomerContract contract;
public Customer(String name, double discountRate) {
this.name = name;
this.discountRate = discountRate;
this.contract = new CustomerContract(dateToday());
}
public double getDiscountRate() {
return discountRate;
}
public void becomePreferred() {
this.discountRate += 0.03;
// 다른 작업들
}
public double applyDiscount(double amount) {
BigDecimal value = BigDecimal.valueOf(amount);
return value.subtract(value.multiply(BigDecimal.valueOf(this.discountRate))).doubleValue();
}
private LocalDate dateToday() {
return LocalDate.now();
}
}
public class CustomerContract {
private LocalDate startDate;
public CustomerContract(LocalDate startDate) {
this.startDate = startDate;
}
}
After
public class Customer {
private String name;
private CustomerContract contract;
public Customer(String name, double discountRate) {
this.name = name;
this.contract = new CustomerContract(dateToday(), discountRate);
}
public void becomePreferred() {
contract.setDiscountRate(contract.getDiscountRate() + 0.03);
// 다른 작업들
}
public double applyDiscount(double amount) {
BigDecimal value = BigDecimal.valueOf(amount);
return value.subtract(value.multiply(BigDecimal.valueOf(contract.getDiscountRate()))).doubleValue();
}
private LocalDate dateToday() {
return LocalDate.now();
}
}
public class CustomerContract {
private LocalDate startDate;
private double discountRate;
public CustomerContract(LocalDate startDate, double discountRate) {
this.startDate = startDate;
this.discountRate = discountRate;
}
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public double getDiscountRate() {
return discountRate;
}
public void setDiscountRate(double discountRate) {
this.discountRate = discountRate;
}
}
=> discountRate 필드 옮기기(Customer -> CustomerContract)
Refactoring 2. 함수 인라인
- 함수 추출하기와 반대되는 리팩토링
- 간혹 함수 본문이 함수 이름보다 의도가 잘 표현될 때가 존재한다.
- 함수 리팩토링이 잘못된 경우에 여러 함수를 인라인하여 커다란 함수를 만든 후, 다음에 다시 함수 추출하기를 적용할 수 있다.
- 단순히 메서드 호출을 감싸는 우회형 메서드라면 인라인으로 없앨 수 있다.
- 상속 구조에서 오버라이딩하고 있는 메서드는 인라인 할 수 없다.
Before
public class Driver {
private int numberOfLateDeliveries;
public Driver(int numberOfLateDeliveries) {
this.numberOfLateDeliveries = numberOfLateDeliveries;
}
public int getNumberOfLateDeliveries() {
return this.numberOfLateDeliveries;
}
}
public class Rating {
public int rating(Driver driver) {
return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}
private boolean moreThanFiveLateDeliveries(Driver driver) {
return driver.getNumberOfLateDeliveries() > 5;
}
}
After
public class Driver {
private int numberOfLateDeliveries;
public Driver(int numberOfLateDeliveries) {
this.numberOfLateDeliveries = numberOfLateDeliveries;
}
public int getNumberOfLateDeliveries() {
return this.numberOfLateDeliveries;
}
}
public class Rating {
public int rating(Driver driver) {
return driver.getNumberOfLateDeliveries() > 5 ? 2 : 1;
}
}
Refactoring 3. 클래스 인라인
- 클래스 추출하기와 반대되는 리팩토링
- 리팩토링을 하는 도중 클래스의 책임을 옮기다 보면 클래스의 존재 유무가 빈약해지는 경우가 발생할 수 있다.
- 먼저, 클래스 인라인을 통해 하나의 클래스로 모아둔 후 다음에 클래스를 추출하는 리팩토링을 적용할 수 있다.
Before
public class Shipment {
private TrackingInformation trackingInformation;
public Shipment(TrackingInformation trackingInformation) {
this.trackingInformation = trackingInformation;
}
public TrackingInformation getTrackingInformation() {
return trackingInformation;
}
public void setTrackingInformation(TrackingInformation trackingInformation) {
this.trackingInformation = trackingInformation;
}
public String getTrackingInfo() {
return this.trackingInformation.display();
}
}
public class TrackingInformation {
private String shippingCompany;
private String trackingNumber;
public TrackingInformation(String shippingCompany, String trackingNumber) {
this.shippingCompany = shippingCompany;
this.trackingNumber = trackingNumber;
}
public String display() {
return this.shippingCompany + ": " + this.trackingNumber;
}
public String getShippingCompany() {
return shippingCompany;
}
public void setShippingCompany(String shippingCompany) {
this.shippingCompany = shippingCompany;
}
public String getTrackingNumber() {
return trackingNumber;
}
public void setTrackingNumber(String trackingNumber) {
this.trackingNumber = trackingNumber;
}
}
After
public class Shipment {
private String shippingCompany;
private String trackingNumber;
public Shipment(String shippingCompany, String trackingNumber) {
this.shippingCompany = shippingCompany;
this.trackingNumber = trackingNumber;
}
public String display() {
return this.shippingCompany + ": " + this.trackingNumber;
}
public String getShippingCompany() {
return shippingCompany;
}
public void setShippingCompany(String shippingCompany) {
this.shippingCompany = shippingCompany;
}
public String getTrackingNumber() {
return trackingNumber;
}
public void setTrackingNumber(String trackingNumber) {
this.trackingNumber = trackingNumber;
}
}
'클린코드' 카테고리의 다른 글
[Refactoring] 10. 데이터 뭉치 (0) | 2022.09.13 |
---|---|
[Refactoring] 9. 기능 편애 (0) | 2022.09.12 |
[Refactoring] 7. 뒤엉킨 변경 (0) | 2022.09.10 |
[Refactoring] 6. 가변 데이터 (0) | 2022.09.09 |
[Refactoring] 5. 전역 데이터 (0) | 2022.09.08 |