함수 추출
함수 추출은 함수를 정의할 때 각 기능들이 어느정도 코드가 길고 읽기가 어렵다면 그 기능을 함수로 추출하는 방법입니다.
예시는 아래와 같습니다. (읽지 않으시는 걸 추천드립니다..) 두 함수가 읽는 것에도 꽤 시간이 들어가고, 공동으로 사용하는 코드도 많은 것 같습니다. 해당 부분들을 각 함수로 추출해보겠습니다.
public class StudyDashboard {
private void printParticipants(int eventId) throws IOException {
GitHub gitHub = GitHub.connect();
GHRepository repository = gitHub.getRepository("whiteship/live-study");
GHIssue issue = repository.getIssue(eventId);
Set<String> participants = new HashSet<>();
issue.getComments().forEach(c -> participants.add(c.getUserName()));
participants.forEach(System.out::println);
}
private void printReviewers() throws IOException {
GitHub gitHub = GitHub.connect();
GHRepository repository = gitHub.getRepository("whiteship/live-study");
GHIssue issue = repository.getIssue(30);
Set<String> reviewers = new HashSet<>();
issue.getComments().forEach(c -> reviewers.add(c.getUserName()));
reviewers.forEach(System.out::println);
}
}
각 함수는 사실 복잡해보이지만, 크게 세 가지 기능으로 분류할 수 있고, 비슷한 동작을 하고 있습니다.
public class StudyDashboard {
private void printParticipants(int eventId) throws IOException {
// 1번 기능
GitHub gitHub = GitHub.connect();
GHRepository repository = gitHub.getRepository("whiteship/live-study");
GHIssue issue = repository.getIssue(eventId);
// 2번 기능
Set<String> participants = new HashSet<>();
issue.getComments().forEach(c -> participants.add(c.getUserName()));
// 3번 기능
participants.forEach(System.out::println);
}
private void printReviewers() throws IOException {
// 1번 기능
GitHub gitHub = GitHub.connect();
GHRepository repository = gitHub.getRepository("whiteship/live-study");
GHIssue issue = repository.getIssue(30);
// 2번 기능
Set<String> reviewers = new HashSet<>();
issue.getComments().forEach(c -> reviewers.add(c.getUserName()));
// 3번 기능
reviewers.forEach(System.out::println);
}
}
먼저, 1번 기능은 GitHub에 연결하고, 특정 repo를 불러와서 Issue를 불러옵니다. 코드도 길어서 생각보다 읽기도 쉽지않고, 공동으로 사용하는 부분이니 정리를 하는 것이 좋습니다.
<참고> IDE에는 보통 Extract method 기능이 내장되어 있기 때문에, 해당 단축키를 익히시면 도움이 되실 것 같습니다.
그러면 간단하게 GithubIssue를 가져온다는 의미의 코드 한 줄로 대체할 수 있습니다.
public class StudyDashboard {
private void printParticipants(int eventId) throws IOException {
GHIssue issue = getGhIssue(eventId);
// 2번 기능
Set<String> participants = new HashSet<>();
issue.getComments().forEach(c -> participants.add(c.getUserName()));
// 3번 기능
participants.forEach(System.out::println);
}
private void printReviewers() throws IOException {
GHIssue issue = getGhIssue(30);
// 2번 기능
Set<String> reviewers = new HashSet<>();
issue.getComments().forEach(c -> reviewers.add(c.getUserName()));
// 3번 기능
reviewers.forEach(System.out::println);
}
private GHIssue getGhIssue(int eventId) throws IOException {
GitHub gitHub = GitHub.connect();
GHRepository repository = gitHub.getRepository("whiteship/live-study");
GHIssue issue = repository.getIssue(eventId);
return issue;
}
}
2번 기능과 3번 기능도 공통적으로 같은 방식으로 리팩토링할 수 있습니다.
기존 함수 (printParticipants, printReviewers) 가 부연설명이 필요 없이 바로 읽을 수 있는 간략한 코드가 되었습니다.
public class StudyDashboard {
private void printParticipants(int eventId) throws IOException {
GHIssue issue = getGhIssue(eventId);
Set<String> participants = getUsernames(issue);
print(participants);
}
private void printReviewers() throws IOException {
GHIssue issue = getGhIssue(30);
Set<String> reviewers = getUsernames(issue);
print(reviewers);
}
private Set<String> getUsernames(GHIssue issue) throws IOException {
Set<String> usernames = new HashSet<>();
issue.getComments().forEach(c -> usernames.add(c.getUserName()));
return usernames;
}
private GHIssue getGhIssue(int eventId) throws IOException {
GitHub gitHub = GitHub.connect();
GHRepository repository = gitHub.getRepository("whiteship/live-study");
GHIssue issue = repository.getIssue(eventId);
return issue;
}
private void print(Set<String> participants) {
participants.forEach(System.out::println);
}
}
함수 추출을 남발하면 함수가 너무 많아질 수 있다는 단점이 있습니다.
하지만 코드가 많고 복잡해 보이는 함수도 이해하는 사람 입장에서는 사실 그렇게까지 복잡하지 않은 경우가 많은데
함수를 잘 추출해서 미래의 내가(?) 또는 다른 분이 코드를 바라보기 쉽게 리팩토링하는 것도 고민해볼 여지가 있는 것 같습니다.
감사합니다.
**
Q. 함수가 많아지면 콜스택이 쌓여서 성능이 오버헤드가 될 수 있지 않나요?
A. 과거의 프로그래밍 언어에서는 그럴 수 있는데 오늘날 프로그래밍 언어들은 컴파일할 때 또는 바이트코드를 처리할 때 많은 최적화(Optimization)이 이루어지기 때문에, 그런 부분까지는 고민할 필요가 없다고 합니다.
출처 : 코딩으로 학습하는 리팩토링 - 인프런 백기선님 강의
'Programming > Refactoring' 카테고리의 다른 글
DTO를 Inner static class로 간결하게 관리하기! (+ domain 분리) (0) | 2022.04.16 |
---|---|
리팩토링 - Switch문을 다형성으로 바꾸기 (0) | 2022.02.27 |
리팩토링 - 반복문 분리 (split loop) (0) | 2022.02.26 |
리팩토링 - 메소드 올리기 (Pull up method) (0) | 2022.02.20 |
조건문 Refactoring 하는 방법! - 1편 (0) | 2022.02.07 |