리팩토링 10. 함수를 명령으로 바꾸기
- etc/리팩토링
- 2023. 4. 29.
들어가기 전
이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.
리팩토링 10. 함수를 명령으로 바꾸기 (Replace Function with Command)
- 함수를 독립적인 객체인, Command로 만들어 사용할 수 있다.
- 커맨드 패턴을 적용하면 다음과 같은 장점을 취할 수도 있다.
- 부가적인 기능으로 undo 기능을 만들 수도 있다.
- 더 복잡한 기능을 구현하는데 필요한 여러 메소드들을 추가할 수 있다.
- 상속이나 템플릿을 활용할 수도 있다.
- 복잡한 메서드를 여러 메서드나 필드를 활용해 쪼갤 수도 있다.
- 대부분의 경우에 '커맨드' 보다는 '함수'를 사용하지만, 커맨드 말고 다른 방법이 없는 경우에만 사용한다.
함수 하나를 독립적인 커맨드 클래스로 분리해내면 다음 장점을 가진다.
- 긴 메서드를 간추리는데 좋다.
- 커맨드로 분리된 오퍼레이션 자체에 필요한 필드, 메서드를 커맨드 클래스로 옮길 수 있다. 즉, 코드 복잡도를 낮출 수 있다.
단점은 그만큼 새로운 클래스가 생성되거나 구조가 변경되기 때문에 이 방면으로 복잡할 수도 있다는 점이다. 장단점이 있는데 언제 사용하면 좋을까? 우선은 '함수 추출'로 처리가 가능한지 고민해본다. 그런데 만약 다음과 같은 생각이 든다면 커맨드로 분리하는 것이 좋다.
- 함수가 이 클래스에 있으면 안될 것 같다
- 이 부분 향후 더 복잡해 질 수도 있을 것 같다
이를 통해 한 클래스에 집중된 코드 복잡도를 분리해줘서 이익을 가져가는 것이다. 손익 분기점 쯔음에 커맨드 클래스로 분리를 해보자는 것이다.
Before
아래 코드 중 FileWriter 쪽을 살펴보자. 이 코드는 마크다운을 생성하는 코드다. 먼저 이 코드만 봤을 때는 의미가 와닿지 않기 때문에 메서드로 추출한 후, execute()라는 이름을 붙여주자. 그런데 한 가지 고민거리가 있다.
execute() 메서드는 향후 확장 가능성이 있다. 예를 들어 지금은 마크다운만 출력하지만, 콘솔 출력 / CSV 출력 등의 작업이 있을 수 있다. 이 부분이 복잡해 질 수 있기 때문에 확장 가능성 + 코드 복잡도의 감소를 고려해서 커맨드 클래스로 분리해본다.
- 해당 코드 부분은 execute() 메서드로 분리한다.
- 커맨드 클래스 역할을 할 StudyPrinter 클래스를 생성하고, execute() 메서드를 옮겨준다. 해당 메서드를 public으로 변경해준다.
- execute() 메서드에서 필요한 header(), markdownForParticipant() 같은 메서드들을 StudyPrinter 클래스로 옮겨준다.
- StudyPrinter에서 필요한 값이 totlaNumberOfEvents이기 때문에 StudyPrinter의 필드로 선언한다.
- 또한 StudyPrinter가 단순히 실행만 하는 녀석이라면, 굳이 execute() 메서드에 Participant 배열을 넘겨줄 필요없이 필드값으로 가진다.
이렇게 작성하면 execute()로 추출된 메서드는 커맨드 클래스로 전환되어 StudyPrinter는 완벽히 '하나의 커맨드'를 표현하는 클래스가 되어버린다.
// StudyDashboard.class
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);
writer.print(markdownForHomework);
});
}
After
리팩토링 후 처음 코드에 비해 절반 가량 코드가 정리가 되었다.
new StudyPrinter(totalNumberOfEvents, participants).execute();
그리고 StudyPrinter 클래스는 다음과 같이 생성되었다.
public class StudyPrinter {
private final int totalNumberOfEvents;
private final List<Participant> participants;
public StudyPrinter(int totalNumberOfEvents, List<Participant> participants) {
this.totalNumberOfEvents = totalNumberOfEvents;
this.participants = participants;
}
public void execute() 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);
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(Participant p) {
return String.format("| %s %s | %.2f%% |\n", p.username(), checkMark(p, this.totalNumberOfEvents),
p.getRate(this.totalNumberOfEvents));
}
/**
* |:white_check_mark:|:white_check_mark:|:white_check_mark:|:x:|
*/
private String checkMark(Participant p, int totalEvents) {
StringBuilder line = new StringBuilder();
for (int i = 1 ; i <= totalEvents ; i++) {
if(p.homework().containsKey(i) && p.homework().get(i)) {
line.append("|:white_check_mark:");
} else {
line.append("|:x:");
}
}
return line.toString();
}
}
Futuremore
print 하는 로직은 향후에 더욱 복잡한 변경에도 대응될 수 있도록 커맨드 클래스로 생성되었다. 따라서 추후 확장할 때는 이런 것들을 고려해서 작성해 볼 수 있다.
- StudyPrinter를 인터페이스나 추상 클래스로 만들고 하위 클래스를 생성 → 다형성으로 접근
- 함께 사용하는 totalNumberofEvents, participants는 상위 클래스로 올려버림.
'etc > 리팩토링' 카테고리의 다른 글
리팩토링 12. 반복문 쪼개기 (0) | 2023.04.29 |
---|---|
리팩토링 11. 조건문 분해하기 (0) | 2023.04.29 |
리팩토링 9. 객체 통째로 넘기기 (0) | 2023.04.29 |
리팩토링 8. 매개변수 객체 만들기 (0) | 2023.04.29 |
리팩토링 7. 임시 변수를 질의 함수로 바꾸기 (0) | 2023.04.29 |