본문 바로가기

클린코드

[Refactoring] 8. 산탄총 수술

 

코딩으로 학습하는 리팩토링 - 인프런 | 강의

리팩토링은 소프트웨어 엔지니어가 갖춰야 할 기본적인 소양 중 하나입니다. 이 강의는 인텔리J와 자바를 사용하여 보다 실용적인 방법으로 다양한 코드의 냄새와 리팩토링 기술을 설명하고 직

www.inflearn.com

| 인프런 - 백기선님의 코딩으로 학습하는 리팩토링 강의를 수강하며 정리한 글입니다.


산탄총 수술
  • 어떠한 변화가 발생하였을 때 여러 모듈, 함수, 클래스를 변경해야 하는 상황
  • 변경 사항이 여러 곳에 흩어진다면 찾아서 수정하기 어렵고, 중요한 변경 사항을 놓칠 수 있다.
  • 관련 리팩토링 기술
    • 함수 옮기기 또는 필드 옮기기 -> 필요한 변경 내역을 하나의 클래스로 모을 수 있다.
    • 여러 함수를 클래스로 묶기 -> 비슷한 데이터를 사용하는 여러 함수를 하나의 클로스로 모을 수 있다.
    • 단계 쪼개기 -> 공통으로 사용되는 함수의 결과물들을 하나로 묶을 수 있다.
    • 함수 인라인, 클래스 인라인 -> 흩어진 로직을 하나로 묶을 수 있다.

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