본문 바로가기

클린코드

[Refactoring] 4. 긴 매개변수 목록

 

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

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

www.inflearn.com

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


긴 매개변수 목록
  • 함수에 매개변수가 많을수록 함수의 역할을 이해하기 힘들어진다.
  • 어떤 매개변수를 다른 매개변수를 통해 알아낼 수 있다면, 매개변수를 질의 함수로 바꾸기를 사용할 수 있다.
  • 기존 자료구조에서 세부적인 데이터를 가져와서 여러 매개변수로 넘기는 대신, 객체 통째로 넘기기를 사용할 수 있다.
  • 일부 매개변수들이 대부분 같이 넘겨진다면, 매개변수 객체 만들기를 사용할 수 있다.
  • 매개변수가 플래그로 사용된다면, 플래그 인수 제거하기를 사용할 수 있다.
  • 여러 함수가 일부 매개변수를 공통적으로 사용한다면, 여러 함수를 클래로 묶기를 통해 매개변수를 해당 클래스의 필드로 만들고 메서드에 전달해야 할 매개변수 목록을 줄일 수 있다.

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

  • 함수의 매개변수 목록은 함수의 다양성을 대변, 짧을 수록 좋다.
  • 한 매개변수를 다른 매개변수를 통해서 알아낼 수 있다면, 이를 중복 매개변수라 한다.
  • 함수를 호출하는 쪽의 책임을 줄이고 함수 내부에서 책임지도록 노력한다.
  • 임시 변수를 질의 함수로 바꾸기함수 선언 변경하기를 통해 리팩토링을 적용한다.
Before
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;
}
After
public double finalPrice() {
    double basePrice = this.quantity * this.itemPrice;
    return this.discountedPrice(basePrice);
}

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

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

Refactoring 2. 플래그 인수 제거하기

  • 플래그는 보통 함수에 매개변수로 전달해서, 함수의 내부 로직을 분기하는데 사용한다.
  • 플래그를 사용한 함수는 차이를 파악하기 힘들다.
  • 조건문 분해하기를 활용할 수 있다.
  • 플래그가 많이 존재한다. -> 해당 함수가 너무 많은 일을 담당하고 있다.
Before
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);
    }
}
After
public static 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 static 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);
}

Refactoring 3. 여러 함수를 클래스로 묶기

  • 비슷한 매개변수 목록을 여러 함수에서 사용한다면, 해당 메서드를 모아 클래스를 만들 수 있다.
  • 클래스 내부로 메서드를 옮기고 데이터를 필드로 만들면 메서드에 전달해야 하는 매개변수의 수를 줄일 수 있다.
Before
public static void main(String[] args) {
	try (FileWriter fileWriter = new FileWriter("participants.md");
        PrintWriter writer = new PrintWriter(fileWriter)) {
        participants.sort(Comparator.comparing(Participant::username));

        writer.print(header(participants.size()));

        participants.forEach(p -> {
            String markdownForHomework = getMarkdownForParticipant(p.username(), p.homework());
            writer.print(markdownForHomework);
        });
    }
}

...
After
public static void main(String[] args) {
	new StudyPrinter(this.totalNumberOfEvents, participants).print(participants);
}

public class StudyPrinter {

    private int totalNumberOfEvents;
    private List<Participant> participants;

    public StudyPrinter(int totalNumberOfEvents, List<Participant> participants) {
        this.totalNumberOfEvents = totalNumberOfEvents;
        this.participants = participants;
    }

    public void print(List<Participant> participants) throws IOException {
        try (FileWriter fileWriter = new FileWriter("participants.md");
             PrintWriter writer = new PrintWriter(fileWriter)) {
            participants.sort(Comparator.comparing(Participant::username));

            writer.print(header(participants.size()));

            participants.forEach(p -> {
                String markdownForHomework = getMarkdownForParticipant(p.username(), p.homework());
                writer.print(markdownForHomework);
            });
        }
    }

    private String header(int totalNumberOfParticipants) {
        StringBuilder header = new StringBuilder(String.format("| 참여자 (%d) |", totalNumberOfParticipants));

        for (int index = 1; index <= this.totalNumberOfEvents; index++) {
            header.append(String.format(" %d주차 |", index));
        }
        header.append(" 참석율 |\n");

        header.append("| --- ".repeat(Math.max(0, this.totalNumberOfEvents + 2)));
        header.append("|\n");

        return header.toString();
    }

    private String getMarkdownForParticipant(String username, Map<Integer, Boolean> homework) {
        return String.format("| %s %s | %.2f%% |\n", username, checkMark(homework), getRate(homework));
    }

    private String checkMark(Map<Integer, Boolean> homework) {
        StringBuilder line = new StringBuilder();
        for (int i = 1 ; i <= this.totalNumberOfEvents ; i++) {
            if(homework.containsKey(i) && homework.get(i)) {
                line.append("|:white_check_mark:");
            } else {
                line.append("|:x:");
            }
        }
        return line.toString();
    }

    private double getRate(Map<Integer, Boolean> homework) {
        long count = homework.values().stream()
                .filter(v -> v == true)
                .count();
        return (double) (count * 100 / this.totalNumberOfEvents);
    }

}

'클린코드' 카테고리의 다른 글

[Refactoring] 6. 가변 데이터  (0) 2022.09.09
[Refactoring] 5. 전역 데이터  (0) 2022.09.08
[Refactoring] 3. 긴 함수  (0) 2022.09.06
[Refactoring] 2. 중복 코드  (0) 2022.09.05
[Refactoring] 1. 이해하기 힘든 이름  (0) 2022.09.05