Clean Code

Java 리팩토링 - 메서드 추출 시 매개변수가 많아질 경우 [3. 객체 통째로 넘기기]

1. 알고자 하는 것

- 메서드를 추출할 때(Extract method), 해당 메서드에 넘겨야 하는 매개변수가 많아질 경우의 리팩토링

1. 임시 변수를 질의 함수로 바꾸기 (Replace Temp with Query)

2. 매개변수 객체 만들기 (Introduce Parameter Object)

3. 객체 통째로 넘기기 (Preserve Whole Object)

 

2. 알게된 것

  • 하나의 객체에서 구할 수 있는 여러 값을 메서드의 매개변수로 넘겨주는 경우, 객체를 통째로 넘겨줄 수 있다.
  • 이를 통해 결과적으로 아래와 같이 매개변수의 개수를 줄일 수 있다.
//(BEFORE)
// 매개변수로 String, Map을 넘겨준다.
private String getMarkdownForParticipant(String username, Map<Integer, Boolean> homework) {
        return String.format("| %s %s | %.2f%% |\n", username,
                checkMark(homework, this.totalNumberOfEvents),
                getRate(homework));
}

// 내부 메서드에도 역시 Map을 넘겨준다.
private double getRate(Map<Integer, Boolean> homework) {
        long count = homework.values().stream()
                .filter(v -> v == true)
                .count();
        return (double) (count * 100 / this.totalNumberOfEvents);
}

// 실제 메서드를 호출 할 때, Participant p 객체의 각 필드를 매개변수로 넘겨준다. (매개변수 2개)
String markdownForHomework = getMarkdownForParticipant(p.username(), p.homework());

==============================================================================

//(AFTER)
// 매개변수로 Participant 객체 자체를 넘겨준다.
private String getMarkdownForParticipant(Participant p) {
        return String.format("| %s %s | %.2f%% |\n", p.getUsername(),
                checkMark(p.getHomework(), this.totalNumberOfEvents),
                getRate(p));
}

// 내부 메서드에도 역시 Participant 객체 자체를 넘겨준다.
private double getRate(Participant p) {
        long count = p.getHomework().values().stream()
                .filter(v -> v == true)
                .count();
        return (double) (count * 100 / this.totalNumberOfEvents);
}

// 실제 메서드를 호출 할 때, Participant p 객체 자체를 넘겨준다. (매개변수 1개)
String markdownForHomework = getMarkdownForParticipant(p);

 

  • 이 때, 메서드가 단순히 객체의 필드(primitive type 등)만을 의존하던 상황 -> 객체 자체에 대한 의존성이 생길 수 있다.
    • 해당 메서드가 다른 위치에서도 쓰일 가능성이 있다면 오브젝트에 대한 의존성은 범용성을 해칠 수 있으니 주의한다.
//(BEFORE)
// 객체에 대한 의존성이 존재하지 않는다.
private String getMarkdownForParticipant(String username, Map<Integer, Boolean> homework) {
        return String.format("| %s %s | %.2f%% |\n", username,
                checkMark(homework, this.totalNumberOfEvents),
                getRate(homework));
}

===============================================================

//(AFTER)
// Participant 객체에 대한 의존성이 생겼다.
// = 해당 메서드는 Participant 객체를 사용하지 않는 다른 곳에서 사용될 수 없다.
// = 범용성을 해칠 수 있다.
private String getMarkdownForParticipant(Participant p) {
        return String.format("| %s %s | %.2f%% |\n", p.getUsername(),
                checkMark(p.getHomework(), this.totalNumberOfEvents),
                getRate(p));
}

 

  • 또한, 메서드의 구현이 대부분 객체의 필드만으로 이루어져 있다면, 메서드의 위치 자체를 고려해봐야한다.
    • 해당 레코드(오브젝트)의 필드만으로 메서드가 구현된다면 해당 메서드는 오브젝트만을 위한 용도로 쓰일 가능성이 있다.
    • 그렇다면 오브젝트 안으로 메서드를 두어 캡슐화를 시키는 방향이 더 나을 수도 있다.
//(BEFORE)
// totalNumberOfEvents를 제외하고는 Participant의 필드만으로 메서드가 구현된다.
private String getMarkdownForParticipant(Participant p) {
        return String.format("| %s %s | %.2f%% |\n", p.getUsername(),
                checkMark(p.getHomework(), this.totalNumberOfEvents),
                getRate(p));
}

private double getRate(Participant p) {
        long count = p.getHomework().values().stream()
                .filter(v -> v == true)
                .count();
        return (double) (count * 100 / this.totalNumberOfEvents);
}

String markdownForHomework = getMarkdownForParticipant(p);

======================================================================

//(AFTER)
// Participant 안에 getMarkdown 메서드를 캡슐화 한다.
public class Participant {
    private String username;
    private Map<Integer, Boolean> homework;
    
    public String getMarkdown(int totalNumberOfEvents) {
       return String.format("| %s %s | %.2f%% |\n", this.username,
                checkMark(totalNumberOfEvents),
                getRate());
    }
    
    private double getRate() {
       ...
    }
    
    private String checkMark(int totalNumberOfEvents) {
       ...
    }
}

String markdownForHomework = p.getMarkdown(this.totalNumberOfEvents);

 

3. 정리

  • 하나의 객체에서 구할 수 있는 여러 값을 메서드의 매개변수로 넘겨주는 경우, 객체를 통째로 넘겨주어 매개변수의 개수를 줄일 수 있다.
  • 이 때, 객체 자체에 의존성이 생기므로 범용적으로 사용될 가능성이 있는 메서드의 경우 의존성을 고려해야 한다.
  • 만약 메서드의 구현이 대부분 객체의 필드에만 의존한다면 해당 객체에서만 전용으로 사용되는 메서드일 수 있다.
    • 이 때에는 차라리 메서드를 객체 내부 메서드로 두어 캡슐화를 고려할 수 있다. (메서드의 위치를 고려한다.)

 


Reference

강의 - 코딩으로 학습하는 리팩토링 (백기선 강사님)