-
[Refactoring] 8. 산탄총 수술클린코드 2022. 9. 11. 15:28
| 인프런 - 백기선님의 코딩으로 학습하는 리팩토링 강의를 수강하며 정리한 글입니다.
산탄총 수술
- 어떠한 변화가 발생하였을 때 여러 모듈, 함수, 클래스를 변경해야 하는 상황
- 변경 사항이 여러 곳에 흩어진다면 찾아서 수정하기 어렵고, 중요한 변경 사항을 놓칠 수 있다.
- 관련 리팩토링 기술
- 함수 옮기기 또는 필드 옮기기 -> 필요한 변경 내역을 하나의 클래스로 모을 수 있다.
- 여러 함수를 클래스로 묶기 -> 비슷한 데이터를 사용하는 여러 함수를 하나의 클로스로 모을 수 있다.
- 단계 쪼개기 -> 공통으로 사용되는 함수의 결과물들을 하나로 묶을 수 있다.
- 함수 인라인, 클래스 인라인 -> 흩어진 로직을 하나로 묶을 수 있다.
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