Language/Java

[리팩토링] 긴 함수

JUNGKEUNG 2022. 8. 6. 23:00

임시 변수를 질의 함수로 바꾸기 (Replace Parameter with Query)

  • 함수의 매개변수 목록은 함수의 다양성을 대변하며, 짧을수록 이해하기 좋다.
  • 어떤 한 매개변수를 다른 매개변수를 통해 알아낼 수 있다면 중복 매개변수라 생각할 수 있다.
  • 매개변수에 값을 전달하는 것은 함수를 호출하는 쪽의 책임이다. 가능하면 함수를 호출하는 쪽의 책임을 줄이고 함수 내부에서 책임지도록 노력한다.
  • 임시 변수를 질의 함수로 바꾸기와 함수 선언 변경하기를 통해 이 리팩토링을 적용한다.

예시

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);
            });

냄새

  • 위 예제 코드 print() 메서드의 마지막 부분이다.
    • rate를 계산해주기 위한 일련의 로직이 길게 작성되어 있으며 그 과정에 있어 임시 변수를 사용한다. 또한 formating 해주는 부분을 읽기 힘들다
    • 위 코드는 의도를 표현한 것이 아닌 구현을 표현한 코드이다. 리팩토링을 해줄 필요가 있다.

해결

  • 임시 변수를 함수화 하자!
  • 소드를 옮기고, 데이터를 필드로 만들면 메소드에 전달해야 하는 매개변수 목록도 줄일 수 있다.

 

 

매개변수 객체 만들기

Introduce Parameter Object

  • 같은 매개변수들이 여러 메소드에 걸쳐 나타난다면 그 매개변수들을 묶은 자료 구조를 만들 수 있다.
  • 그렇게 만든 자료구조
    • 해당 데이터간의 관계를 보다 명시적으로 나타낼 수 있다.
    • 함수에 전달할 매개변수 개수를 줄일 수 있다.
    • 도메인을 이해하는데 중요한 역할을 하는 클래스로 발전할 수도 있다

예시

public String getMarkdownForParticipant(int totalNumberOfEvents, Participant p) {
	....
}

private String header(int totalNumberOfEvents, int totalNumberOfParticipants) {
	....
}

냄새 

  • 반복되는 인자를 갖는 함수들이다.
  • 위의 설명처럼 같은 매개변수들이 여러 메소드에 걸쳐 나타난다면 그 매개변수들을 묶은 자료구조를 만들 수 있다.

해결

  • record 와 같은 자료구조를 만들어 해결한다.
  • 함수 내부에서 자주 쓰이는 공통적인 매개변수를 필드로 바꾼다.

 

 

객체 통째로 넘기기

Preserve Whole Object

  • 어떤 한 레코드에서 구할 수 있는 여러 값들을 함수에 전달하는 경우, 해당 매개변수를 레코드 하나로 교체할 수 있다.
  • 매개변수 목록을 줄일 수 있다. 
  • 이 기술을 적용하기 전에 의존성을 고려해야 한다.
  • 어쩌면 해당 메소드의 위치가 적절하지 않을 수도 있다

예시

private String getMarkdownForParticipant(String username, Map<Integer, Boolean> homework) {
        return String.format("| %s %s | %.2f%% |\n", username,
                checkMark(homework, this.totalNumberOfEvents),
                getRate(homework));
    }

냄새

  • 매개변수를 객체화할 수 있다.

해결

  • 위의 코드의 매개변수를 Participant 객체로 만들어 매개변수를 줄인다.
  • 더 나아가서 내부의 getRate() 함수도 Participant 내부에서 사용한다.

 

함수를 명령으로 바꾸기

Replace Function with Command

  • 함수를 독립적인 객체인, Command로 만들어 사용할 수 있다.
  • 커맨드 패턴을 적용하면 다음과 같은 장점을 취할 수 있다.
    • 부가적인 기능으로 undo 기능을 만들 수도 있다.
    • 더 복잡한 기능을 구현하는데 필요한 여러 메소드를 추가할 수 있다.
    • 상속이나 템플릿을 활용할 수도 있다.
    • 복잡한 메소드를 여러 메소드나 필드를 활용해 쪼갤 수도 있다.
  • 대부분의 경우에 “커맨드” 보다는 “함수”를 사용하지만, 커맨드 말고 다른 방법이 없는 경우에만 사용한다.

예시

private void print() throws IOException, InterruptedException {
        GitHub gitHub = GitHub.connect();
        GHRepository repository = gitHub.getRepository("whiteship/live-study");
        List<Participant> participants = new CopyOnWriteArrayList<>();

        ExecutorService service = Executors.newFixedThreadPool(8);
        CountDownLatch latch = new CountDownLatch(totalNumberOfEvents);

        for (int index = 1 ; index <= totalNumberOfEvents ; index++) {
            int eventId = index;
            service.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        GHIssue issue = repository.getIssue(eventId);
                        List<GHIssueComment> comments = issue.getComments();

                        for (GHIssueComment comment : comments) {
                            String username = comment.getUserName();
                            boolean isNewUser = participants.stream().noneMatch(p -> p.username().equals(username));
                            Participant participant = null;
                            if (isNewUser) {
                                participant = new Participant(username);
                                participants.add(participant);
                            } else {
                                participant = participants.stream().filter(p -> p.username().equals(username)).findFirst().orElseThrow();
                            }

                            participant.setHomeworkDone(eventId);
                        }

                        latch.countDown();
                    } catch (IOException e) {
                        throw new IllegalArgumentException(e);
                    }
                }
            });
        }

        latch.await();
        service.shutdown();

        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.username(), p.homework());
                writer.print(markdownForHomework);
            });
        }
    }

냄새

  • print() 함수 내부에서 실질적으로 print를 해주는 로직을 객체화하여 사용하면 깔끔해지지 않을까?

해결

  • 새로운 클래스를 생성 후 해당 클래스 내부에서 연관된 함수를 구현한다.

 

 

참고


 

 

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

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

www.inflearn.com

 

'Language > Java' 카테고리의 다른 글

긴 매개변수 목록  (0) 2022.08.15
긴 함수(2)  (0) 2022.08.14
Optional  (0) 2022.07.02
Java8 Stream  (0) 2022.07.02
인터페이스 기본 메소드와 스태틱 메소드  (0) 2022.06.19