리팩토링 9. 객체 통째로 넘기기
- etc/리팩토링
- 2023. 4. 29.
들어가기 전
이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.
리팩토링 9. 객체 통째로 넘기기 (Preserve Whole Object)
- 어떤 한 레코드에서 구할 수 있는 여러 값들을 메서드에 전달하는 경우, 해당 매개변수를 레코드 하나로 교체할 수 있다.
- 객체를 통째로 넘기면서, 매개변수 목록을 줄일 수 있다.
- 향후에 추가할지도 모를 매개변수까지도 줄일 수 있음. 레코드에 포함하는 방식
- 이 기술을 적용하기 전에 함수가 해당 인스턴스에 의존해도 되는지를 고려해야 함.
- 확장 가능성을 고려해야한다. 만약 해당 메서드가 특정 인스턴스에 의존한다고 하면, 다른 도메인에서는 해당 메서드를 사용할 수 없을 수 있다.
- 예를 들어 Order 도메인 객체에 메서드가 의존하게 된다면, 이 메서드는 Person 도메인에서는 전혀 사용할 수 없게 되어버리기 때문이다.
- 어쩌면 해당 메서드의 위치가 적절하지 않을 수도 있다. (기능 편애 'Feature Envy" 냄새에 해당한다)
메서드에 전달되는 매개변수가 여러 개 있을 때, 어쩌면 이 매개변수들이 하나의 인스턴스에 속한 경우도 있을 수 있다. 이 때, 각 매개변수를 넘기는 것이 아니라 '인스턴스 통째'로 넘기는 방법도 고려해 볼 수 있다. 각각의 장단점은 다음과 같다.
- 장점 : 전달되는 매개변수의 숫자를 줄여 읽기 쉬운 메서드를 생성한다.
- 단점 : 메서드가 특정 객체에 대한 의존성이 생김.
따라서 '객체 통째로 넘기기'를 통해서 매개변수를 줄이려고 할 때, 메서드가 이 '객체'에 의존해도 되는지에 대한 고민이 필요하다. 이 부분은 확장 가능성을 고려해야한다. 이후에 고려해야 할 부분은 '해당 메서드의 위치가 적절하지 않을 수도 있다'라는 것을 고려해야 한다.
Before
리팩토링이 되는 대상은 getMarkdownForParticipant()라는 메서드다. 이 메서드를 살펴보면 이렇다.
- username, Map을 매개변수로 전달받는다.
- 이 매개변수는 p.username(), p.homework() 메서드를 통해서 전달받는다. (Participant 타입의 객체 p)
getMarkdownForParticipant()라는 메서드가 전달받는 두 개의 매개변수는 모두 Participant 인스턴스가 가지고 있는 필드 값이다. 따라서 Participant 인스턴스를 통째로 넘겨서 매개변수의 숫자를 2 → 1로 줄일 수 있다. 이렇게 하려고 했을 때 고려해야 할 부분은 getMarkdownForParticipant() 메서드가 Participant 도메인에서만 사용되어도 괜찮은지다.
- getMarkdownForParticipant() 메서드가 Participant를 매개변수로 받으면, Participant 도메인에서만 사용된다.
- 만약 다른 도메인에서도 사용될 계획이 있는 메서드라면 기존처럼 Primitive 타입을 받아야 한다.
그런데 이 메서드는 Participant에서만 사용될 것이기 때문에 매개변수를 Participant로 사용하기로 했다!
double getRate(Map<Integer, Boolean> homework) {
long count = homework.values().stream()
.filter(v -> v == true)
.count();
return (double) (count * 100 / this.totalNumberOfEvents);
}
// 현재 Participant 객체의 값을 각각 primitive 타입으로 받고 있음.
// 이 메서드가 Participant 객체에만 의존해도 될까? (다른 도메인에서는 사용되지 않을까?)
private String getMarkdownForParticipant(String username, Map<Integer, Boolean> homework) {
return String.format("| %s %s | %.2f%% |\n", username,
checkMark(homework, this.totalNumberOfEvents),
getRate(homework));
}
Before2
getMakrdownForParticipant 클래스에서 Participant에 의존하기로 결정했다. 따라서 getMarkdownForParticipant() 내부에 있는 메서드들도 Participant에 따라서 정리할 수 있는지 살펴봐야하는데, 특정 메서드가 이 클래스에 있는 것이 타당한지를 살펴보는 것이다. getRate() 함수가 그 예시다. getRate()는 StudyDashboard 클래스에 존재하는 것이 맞을까?
- getRate()에 필요한 모든 값으니 Participant의 homework 필드에 존재한다. 필요한 모든 값은 Participant 인스턴스에 존재함.
- 또한 Participant의 참석율이기 때문에 문맥상 Participant에 있는 것이 더욱 적절함.
이런 이유 때문에 getRate()는 StudyDashboard 클래스보다는 Participant 클래스로 이동하는 것이 더욱 적절할 것이다.
// 얘는 이 곳에 위치하는게 적절하지 않을 수 있다.
// 참석율을 계산하는 것인데, 필요한 모든 값은 Participant 객체 내부에 존재함.
// 특정 참석자의 참가율은 참석자 인스턴스에서 구하는게 더 합리적이다.
double getRate(Map<Integer, Boolean> homework) {
long count = homework.values().stream()
.filter(v -> v == true)
.count();
return (double) (count * 100 / this.totalNumberOfEvents);
}
After
위의 내용을 참고해서 아래와 같이 리팩토링 했다.
- getMarkdownForParticipant()는 특정 도메인 Participant에 의존하기로 결정되었다. 다른 곳에서 사용되지 않을 것이기 때문이다.
- 따라서 getMarkdownForParticipant() 내부에서 사용되고 있는 메서드들의 수정도 필요하다. getRate()는 Participant 객체의 값으로만 처리되는데, 의미를 봤을 때 Participant 인스턴스의 멤버로 있는 것이 더욱 합당하기 때문에 이동함.
// StudyDashboard.class
// 현재 Participant 객체의 값을 각각 primitive 타입으로 받고 있음.
// 이 메서드가 Participant 객체에만 의존해도 될까? (다른 도메인에서는 사용되지 않을까?)
// -> 다른 도메인에서는 사용되지 않을 것이기 때문에 Participant 객체를 받도록 수정
private String getMarkdownForParticipant(Participant participant) {
return String.format("| %s %s | %.2f%% |\n",
participant.username(),
checkMark(participant.homework(), this.totalNumberOfEvents),
participant.getRate(this.totalNumberOfEvents));
}
public record Participant(String username, Map<Integer, Boolean> homework) {
public Participant(String username) {
this(username, new HashMap<>());
}
public void setHomeworkDone(int index) {
this.homework.put(index, true);
}
public double getRate(int totalNumberOfEvents) {
long count = this.homework.values().stream()
.filter(v -> v == true)
.count();
return (double) (count * 100 / totalNumberOfEvents);
}
}
FutureMore
이렇게 Participant Record에는 메서드가 하나 추가되었다. 이렇게 Record에 메서드가 추가되다보면, 이 녀석은 언젠가 Record에서 하나의 Class로 발전할 수도 있게 될 것이다.
'etc > 리팩토링' 카테고리의 다른 글
리팩토링 11. 조건문 분해하기 (0) | 2023.04.29 |
---|---|
리팩토링 10. 함수를 명령으로 바꾸기 (0) | 2023.04.29 |
리팩토링 8. 매개변수 객체 만들기 (0) | 2023.04.29 |
리팩토링 7. 임시 변수를 질의 함수로 바꾸기 (0) | 2023.04.29 |
냄새 3. 긴 함수 (0) | 2023.04.29 |