Programming/Refactoring

리팩토링 - 함수 추출 (Extract function)

JaeHoney 2022. 2. 19. 19:16

함수 추출

함수 추출은 함수를 정의할 때 각 기능들이 어느정도 코드가 길고 읽기가 어렵다면 그 기능을 함수로 추출하는 방법입니다.

 

예시는 아래와 같습니다. (읽지 않으시는 걸 추천드립니다..) 두 함수가 읽는 것에도 꽤 시간이 들어가고, 공동으로 사용하는 코드도 많은 것 같습니다. 해당 부분들을 각 함수로 추출해보겠습니다.

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)이 이루어지기 때문에, 그런 부분까지는 고민할 필요가 없다고 합니다.

 

 


 

출처 : 코딩으로 학습하는 리팩토링 - 인프런 백기선님 강의
 

코딩으로 학습하는 리팩토링 - 인프런 | 강의

리팩토링은 소프트웨어 엔지니어가 갖춰야 할 기본적인 소양 중 하나입니다. 이 강의는 인텔리J와 자바를 사용하여 보다 실용적인 방법으로 다양한 코드의 냄새와 리팩토링 기술을 설명하고 직

www.inflearn.com