산탄총 수술(Shotgun Surgery)

박상훈·2022년 8월 18일
0
어떤 한 변경 사항이 생겼을 때 여러 모듈을 수정해야 하는 경우
변경 사항이 여러곳에 흩어진다면 찾아서 고치기 어렵고 중요한 변경 사항을 놓칠 수 있다

필드 옮기기(Move Field)

  • 좋은 데이터 구조를 가지고 있다면 데이터에 기반한 행위를 코드로 옮기는 것도 간편하고 단순하다
  • 필드를 옮기는 단서
    • 어떤 데이터를 항상 어떤 레코드와 함께 전달
    • 어떤 레코드를 변경할 때 다른 레코드에 있는 필드를 변경해야 함
    • 여러 레코드에 동일한 필드를 수정
    • 여기서 말하는 레코드는 클래스 또는 객체를 의미함

Customer 레코드에 있는 discountRate 필드는 해당 레코드의 역할에 있기엔 타당하지 않다
역할에 맞도록 CustomerContract 레코드로 옮겨야 한다

before

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();
    }
}

class CustomerContract {
    private LocalDate startDate;
  
    public CustomerContract(LocalDate startDate) {
        this.startDate = startDate;
    }   
}

after

class Customer {
    private String name;
    private CustomerContract contract;
  
    public Customer(String name, double discountRate) {
        this.name = name;
        this.contract = new CustomerContract(dateToday(), discountRate);
    }
  
    public double getDiscountRate() {
        return this.contract.getDiscountRate();
    }
  
    public void becomePreferred() {
        this.contract.setDiscountRate(this.getDiscountRate() + 0.03);
    }
  
    public double applyDiscount(double amount) {
        BigDecimal value = BigDecimal.valueOf(amount);
        return value.subtract(value.multiply(BigDecimal.valueOf(this.getDiscountRate()))).doubleValue();
    }
  
    private LocalDate dateToday() {
        return LocalDate.now();
    }
}

class CustomerContract {
    private LocalDate startDate;
    private double discountRate;
    
    // ...생성자, getter, setter
}

함수 인라인(Inline Function)

  • 함수 추출하기(Extract Function)의 반대에 해당하는 리팩토링
  • 함수 본문이 이름 만큼 의도를 저 잘 표현한 경우
  • 메소드 호출을 감싸는 우회형(indirection) 메서드의 경우 인라인으로 없앨 수 있음
  • 상속 구조에서 오버라이딩 하고 있는 메소드는 인라인할 수 없음

moreThanFiveLateDeliveries 메서드는 함수 추출하기에 의해 분할되어 있는 것 보다
함수 인라인 리팩토링에 더 적합한 케이스이므로 메서드 하나로 그 의미를 쉽게 이해할 수 있다

before

class Refactoring {
    public int rating(Driver driver) {
        return moreThanFiveLateDeliveries(driver) ? 2 : 1;
    }
  
    private boolean moreThanFiveLateDeliveries(Driver driver) {
        return driver.getNumberOfLateDeliveries() > 5;
    }
}

after

class Refactoring {
    public int rating(Driver driver) {
        return driver.getNumberOfLateDeliveries() > 5 ? 2 : 1;
    }   
}

클래스 인라인(Inline Class)

  • 클래스 추출하기에 반대
  • 리팩토링 하는 중에 클래스의 책임을 옮기다보면 클래스의 존재 이유가 빈약한 경우

TrackingInformation 의 필드들의 의미가 Shipment 클래스에 있을 때 더 타당한 케이스
클래스 인라인 리팩토링을 적용해 모든 필드 메서드들을 옮기고 불필요한 클래스는 삭제

before

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();
    }
}

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

class Shipment {
    private String shippingCompany;
    private String trackingNumber;
  
    public Shipment(String shippingCompany, String trackingNumber) {
        this.shippingCompany = shippingCompany;
        this.trackingNumber = trackingNumber;
    }
  
    public String getTrackingInfo() {
        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;
    }  
}
profile
엔지니어

0개의 댓글