리팩토링 7. 임시 변수를 질의 함수로 바꾸기
- etc/리팩토링
- 2023. 4. 29.
들어가기 전
이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.
리팩토링 7. 임시 변수를 질의 함수로 바꾸기 (Replace Temp With Query)
- 임시변수 사용은 다음 장점을 가짐.
- 반복해서 동일한 식을 계산하는 것을 피할 수 있음.
- 이름을 사용해 의미를 표현할 수도 있음.
- 긴 함수를 리팩토링 할 때, 임시 변수를 함수 추출하기로 분리한다면 '함수 추출하기'로 리팩토링한 함수에 전달되어야 할 매개변수를 줄일 수 있음.
임시변수의 장점
메서드 내부에서 사용되는 값들이 있는데, 이런 값들은 임시변수를 이용해서 명시해주는 것이 좋다. 좋은 이유는 두 가지다.
- 반복되는 계산을 하지 않아도 된다.
- 변수가 이름을 가지기 때문에 의미를 잘 표현할 수 있다.
따라서 반복되는 수식이라면 임시변수를 생성하는 것 역시 하나의 '리팩토링'으로 볼 수 있다. 그런데 긴 함수에서 너무 많은 임시변수가 생기면, 정작 함수 추출하기를 할 때 생성되는 함수의 매개변수의 숫자가 많아질 수 있다는 것이다. 함수의 매개변수가 많아지는 것 역시 경계해야 할 부분이다.
임시변수를 사용하면서, 함수 추출하기의 매개변수를 줄일 수 있는 방법은 없을까?
임시 변수를 질의 함수로 추출하자
질의 함수라는 것은 임시 변수를 리턴해주는 함수를 의미한다. 다음 조건을 만족하면 함수 추출하기로 생성되는 함수에 새로운 매개변수를 추가하지 않아도, 생성된 함수에서 임시변수를 사용할 수 있다.
함수 추출하기에서 생성된 함수의 매개변수로 질의 함수를 실행할 수 있는 경우
어떤 내용인지는 글만 보면 잘 이해가 되지 않으니, 코드로 확인해보자.
실습 (Before)
String.format 코드를 리팩토링 해보고자 한다.
- String.format 코드는 긴 코드다. 왜냐하면 세부 구현에만 집중되어 있는 코드이기 때문에 언듯 보았을 때, 무슨 일을 하는지 전혀 알 수 없다. 따라서 리팩토링 대상이 된다.
- String.format을 함수 추출하기로 추출하는 경우, 전달해야하는 매개변수가 3개다. 매개변수도 제법 많아진다.
String.format() 함수에는 totalNumberOfEvents, p, rate 라는 매개변수가 넘어가게 된다. 여기서 한 가지 살펴볼 부분은 임시변수 rate다.
임시변수 rate는 특정 수식을 통해서 계산이 되는데, 계산될 때 필요한 값은 totalNumberOfEvents, p만 있으면 된다. 즉, 함수 추출하기로 만들어진 함수 getMarkdownForParticipant()가 요구하는 매개변수로만 만들어 질 수 있다. 따라서 rate를 getRate() 메서드로 추출한 다음에 getMarkdownForParticipant()에서 getRate()를 호출해서 rate를 참조하는 형식으로 구현할 수 있다.
따라서 리팩토링은 다음과 같이 해야한다.
- getMarkdownForParticipant() 메서드를 생성함. 이것은 String.format()의 긴 함수를 의미있는 이름으로 바꾸어 보다 짧은 함수로 바꾸는 작업임.
- 쿼리 메서드 getRate()를 추출해서 getMarkdownForParticipant()에서 호출하도록 한다. 이를 통해 매개변수 rate를 getMarkdwonForParticipant()에 전달하지 않도록 한다.
private void print() throws IOException, InterruptedException {
...
try (FileWriter fileWriter = new FileWriter("participants.md");
PrintWriter writer = new PrintWriter(fileWriter)) {
participants.sort(Comparator.comparing(Participant::username));
writer.print(header(totalNumberOfEvents, participants.size()));
participants.forEach(p -> {
long count = p.homework().values().stream()
.filter(v -> v == true)
.count();
double rate = count * 100 / totalNumberOfEvents;
// 이 부분을 리팩토링 할 수 있다.
// 한 줄 짜리 코드지만 구현에 집중해있어서, 코드만 봤을 때는 무엇을 하는지 전혀 알 수 없다.
String markdownForHomework = String.format("| %s %s | %.2f%% |\n", p.username(), checkMark(p, totalNumberOfEvents), rate);
writer.print(markdownForHomework);
});
}
}
실습 (After)
위의 리팩토링을 반영하면 아래와 같은 결과를 확인할 수 있다.
...
try (FileWriter fileWriter = new FileWriter("participants.md");
PrintWriter writer = new PrintWriter(fileWriter)) {
participants.sort(Comparator.comparing(Participant::username));
writer.print(header(totalNumberOfEvents, participants.size()));
participants.forEach(p -> {
// 함수 추출
double rate = getRate(totalNumberOfEvents, p);
// 이 부분을 리팩토링 할 수 있다.
// 한 줄 짜리 코드지만 구현에 집중해있어서, 코드만 봤을 때는 무엇을 하는지 전혀 알 수 없다.
// 함수로 추출했더니, 전달되는 매개변수가 3개다. 좀 더 줄이는 것이 좋다.
// String markdownForHomework = getMarkdownForParticipant(totalNumberOfEvents, p, rate);
String markdownForHomework = getMarkdownForParticipant(totalNumberOfEvents, p);
writer.print(markdownForHomework);
});
}
}
// 임시변수를 위한 쿼리 함수 생성
private double getRate(int totalNumberOfEvents, Participant p) {
long count = p.homework().values().stream()
.filter(v -> v == true)
.count();
return count * 100 / totalNumberOfEvents;
}
// 함수 추출
private String getMarkdownForParticipant(int totalNumberOfEvents, Participant p) {
return String.format("| %s %s | %.2f%% |\n",
p.username(),
checkMark(p, totalNumberOfEvents),
getRate(totalNumberOfEvents, p));
}
'etc > 리팩토링' 카테고리의 다른 글
리팩토링 9. 객체 통째로 넘기기 (0) | 2023.04.29 |
---|---|
리팩토링 8. 매개변수 객체 만들기 (0) | 2023.04.29 |
냄새 3. 긴 함수 (0) | 2023.04.29 |
리팩토링 17. 변수 캡슐화하기 (0) | 2023.04.27 |
리팩토링 : 냄새 5 - 전역 데이터 (0) | 2023.04.27 |