리팩토링 - 냄새 4. 긴 매개변수 목록

김상운(개발둥이)·2022년 3월 30일
2

리팩토링

목록 보기
5/17
post-thumbnail

들어가기

해당 포스팅은 인프런 백기선님의 '리팩토링'을 학습 후 정리한 내용입니다.



냄새 4. 긴 매개변수 목록

Long Parameter List

  • 어떤 함수에 매개변수가 많을수록 함수의 역할을 이해하기 어려워진다.
  • 과연 그 함수는 한가지 일을 하고 있는게 맞는가?
  • 불필요한 매개변수는 없는가?
  • 하나의 레코드로 뭉칠 수 있는 매개변수 목록은 없는가?
  • 어떤 매개변수를 다른 매개변수를 통해 알아낼 수 있다면, “매개변수를 질의 함수로 바꾸기 (Replace Parameter with Query)”를 사용할 수 있다.
  • 기존 자료구조에서 세부적인 데이터를 가져와서 여러 매개변수로 넘기는 대신, “객체 통째로 넘기기 (Preserve Whole Object)”를 사용할 수 있다.
  • 일부 매개변수들이 대부분 같이 넘겨진다면, “매개변수 객체 만들기 (Introduce Parameter Object)”를 적용할 수 있다.
  • 매개변수가 플래그로 사용된다면, “플래그 인수 제거하기 (Remove Flag Argument)”를 사용할 수 있다.
  • 여러 함수가 일부 매개변수를 공통적으로 사용한다면 “여러 함수를 클래스로 묶기 (Combine Functions into Class)”를 통해 매개변수를 해당 클래스의 필드로 만들고 매서드에 전달해야 할 매개변수 목록을 줄일 수 있다.


매개변수를 질의 함수로 바꾸기

Replace Parameter with Query

  • 함수의 매개변수 목록은 함수의 다양성을 대변하며, 짧을수록 이해하기 좋다.
  • 어떤 한 매개변수를 다른 매개변수를 통해 알아낼 수 있다면 “중복 매개변수”라 생각할 수 있다.
  • 매개변수에 값을 전달하는 것은 “함수를 호출하는 쪽”의 책임이다. 가능하면 함수를 호출하는 쪽의 책임을 줄이고 함수 내부에서 책임지도록 노력한다.
  • “임시 변수를 질의 함수로 바꾸기”와 “함수 선언 변경하기”를 통해 이 리팩토링을 적용한다.

예제 코드

public class Order {

    private int quantity;

    private double itemPrice;

    public Order(int quantity, double itemPrice) {
        this.quantity = quantity;
        this.itemPrice = itemPrice;
    }

    public double finalPrice() {
        double basePrice = this.quantity * this.itemPrice;
        int discountLevel = this.quantity > 100 ? 2 : 1;
        return this.discountedPrice(basePrice, discountLevel);
    }

    private double discountedPrice(double basePrice, int discountLevel) {
        return discountLevel == 2 ? basePrice * 0.90 : basePrice * 0.95;
    }
}

냄새

discountedPrice() 함수의 파라미터 discountLevel 은 다른 파라미터를 통해 값을 유츄할 수 있다.

해결

함수로 추출하자!

리팩토링 후

package me.whiteship.refactoring._04_long_parameter_list.practice._01;

public class Order {

    private int quantity;

    private double itemPrice;

    public Order(int quantity, double itemPrice) {
        this.quantity = quantity;
        this.itemPrice = itemPrice;
    }

    public double finalPrice() {
        double basePrice = this.quantity * this.itemPrice;
        return this.discountedPrice(basePrice);
    }

    private int getDiscountLevel() {
        return this.quantity > 100 ? 2 : 1;
    }

    private double discountedPrice(double basePrice) {
        return getDiscountLevel() == 2 ? basePrice * 0.9 : basePrice * 0.95;
    }
}

설명

  • getDiscountLevel() 이라는 함수로 추출하여 discountedPrice() 함수 내부에서 호출하여 사용한다.
  • 따라서 discountedPrice() 함수의 매개변수를 줄일 수 있다.


플래그 인수 제거하기

Remove Flag Argument

  • 플래그는 보통 함수에 매개변수로 전달해서, 함수 내부의 로직을 분기하는데 사용한다.
  • 플래그를 사용한 함수는 차이를 파악하기 어렵다.
    • bookConcert(customer, false), bookConcert(customer, true)
    • bookConcert(customer), premiumBookConcert(customer)
  • 조건문 분해하기 (Decompose Condition)를 활용할 수 있다.

예제 코드

public class Shipment {

    public LocalDate deliveryDate(Order order, boolean isRush) {
        if (isRush) {
            int deliveryTime = switch (order.getDeliveryState()) {
                case "WA", "CA", "OR" -> 1;
                case "TX", "NY", "FL" -> 2;
                default -> 3;
            };
            return order.getPlacedOn().plusDays(deliveryTime);
        } else {
            int deliveryTime = switch (order.getDeliveryState()) {
                case "WA", "CA" -> 2;
                case "OR", "TX", "NY" -> 3;
                default -> 4;
            };
            return order.getPlacedOn().plusDays(deliveryTime);
        }
    }
}

냄새

deliveryDate() 함수 내부에서 급 배송을 의미하는 isRush 에 따라 배송 날짜를 계산해주는 함수이다. 하지만 함수 매개변수에 플래그가 있으면 함수가 많은 일을 하고있는 것이다.

해결

함수를 분리하자!

리팩토링 후

public class Shipment {

    public LocalDate regularDeliveryDate(Order order) {
        int deliveryTime = switch (order.getDeliveryState()) {
            case "WA", "CA" -> 2;
            case "OR", "TX", "NY" -> 3;
            default -> 4;
        };
        return order.getPlacedOn().plusDays(deliveryTime);
    }

    public LocalDate rushDeliveryDate(Order order) {
        int deliveryTime = switch (order.getDeliveryState()) {
            case "WA", "CA", "OR" -> 1;
            case "TX", "NY", "FL" -> 2;
            default -> 3;
        };
        return order.getPlacedOn().plusDays(deliveryTime);
    }
}

설명

조건에 맞는 함수로 분리하여 필요한 함수를 호출해 사용하였다.



여러 함수를 클래스로 묶기

Combine Functions into Class

  • 비슷한 매개변수 목록을 여러 함수에서 사용하고 있다면 해당 메소드를 모아서 클래스를 만들 수 있다.
  • 클래스 내부로 메소드를 옮기고, 데이터를 필드로 만들면 메소드에 전달해야 하는 매개변수 목록도 줄일 수 있다.

예제 코드

예제 코드는 이전 포스팅을 참조하자. 보러 가기

냄새

각 함수에서 totalNumberOfEvents, participants 와 같은 비슷한 매개변수를 여러 함수에서 사용하고 있다.

해결

필요한 데이터와 메서드를 옮겨 클래스로 만들어 해결할 수 있다.

리팩토링 후

리팩토링 후 코드는 이전 포스팅을 참조하자. 보러가기

설명

  • 관련 있는 매개변수들이 여기저기 흩어져 있어 관련있는 매개변수를 클래스로 만들었다.
  • 가급적이면 관련있는 데이터를 한곳으로 옮기고 메서드를 옮기다 보면 메서드에 전달되는 매개변수를 줄일 수 있다.


profile
공부한 것을 잊지 않기 위해, 고민했던 흔적을 남겨 성장하기 위해 글을 씁니다.

0개의 댓글