tonykang22 / study

0 stars 0 forks source link

[Refactoring] 냄새 3. 긴 함수 #5

Open tonykang22 opened 2 years ago

tonykang22 commented 2 years ago

냄새 3. 긴 함수 (Long Function)


리팩토링 7. 임시 변수를 질의 함수로 바꾸기 (Replace Temp With Query)


예시

public class StudyDashboard {
        .
        .
        .
    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 -> {
                    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();
        double rate = count * 100 / totalNumberOfEvents;
        return rate;
    }

    private String getMarkdownForParticipant(int totalNumberOfEvents, Participant p) {
        return String.format("| %s %s | %.2f%% |\n", p.username(), 
                        checkMark(p, totalNumberOfEvents), getRate(totalNumberOfEvents, p));

    }
}


리팩토링 8. 매개변수 객체 만들기 (Introduce Parameter Object)


예시

public record Participant(String username, Map<Integer, Boolean> homework) {
    public Participant(String username) {
        this(username, new HashMap<>());
    }

    public double getRate(double total) {
        long count = this.homework.values().stream()
                .filter(v -> v == true)
                .count();
        return count * 100 / total;
    }

    public void setHomeworkDone(int index) {
        this.homework.put(index, true);
    }
}
public class StudyDashboard {
        .
        .
        .
    private double getRate(int totalNumberOfEvents, Participant p) {
            long count = p.homework().values().stream()
                    .filter(v -> v == true)
                    .count();
            double rate = count * 100 / totalNumberOfEvents;
            return rate;
        }

        private String getMarkdownForParticipant(int totalNumberOfEvents, Participant p) {
            return String.format("| %s %s | %.2f%% |\n", p.username(), 
                            checkMark(p, totalNumberOfEvents), getRate(totalNumberOfEvents, p));
        }
}


public class StudyDashboard {

    private final int totalNumberOfEvents;

    public StudyDashboard(int totalNumberOfEvents) {
        this.totalNumberOfEvents = totalNumberOfEvents;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        StudyDashboard studyDashboard = new StudyDashboard(15);
        studyDashboard.print();
    }

    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(participants.size()));

            participants.forEach(p -> {
                String markdownForHomework = getMarkdownForParticipant(p);
                writer.print(markdownForHomework);
            });
        }
    }

    private double getRate(Participant p) {
        long count = p.homework().values().stream()
                .filter(v -> v == true)
                .count();
        double rate = count * 100 / this.totalNumberOfEvents;
        return rate;
    }

    private String getMarkdownForParticipant(Participant p) {
        return String.format("| %s %s | %.2f%% |\n", p.username(),
                checkMark(p, this.totalNumberOfEvents), getRate(p));
    }
}


리팩토링 9. 객체 통째로 넘기기 (Preserve Whole Object)

예시

public class StudyDashboard {
        .
        .
        .
    private void print() throws IOException, InterruptedException {
        .
        .
        .
        participants.forEach(p -> {
                String markdownForHomework = getMarkdownForParticipant(p.username(), p.homework());
                writer.print(markdownForHomework);
            });
        }
    }

    private double getRate(Map<Integer, Boolean> homework) {
        long count = homework.values().stream()
                .filter(v -> v == true)
                .count();
        return (double) (count * 100 / this.totalNumberOfEvents);
    }

    private String getMarkdownForParticipant(String username, Map<Integer, Boolean> homework) {
        return String.format("| %s %s | %.2f%% |\n", username,
                checkMark(homework, this.totalNumberOfEvents),
                getRate(homework));
    }
}
        public class StudyDashboard {
        .
        .
        .
    private void print() throws IOException, InterruptedException {
        .
        .
        .
        participants.forEach(p -> {
                String markdownForHomework = getMarkdownForParticipant(p);
                writer.print(markdownForHomework);
            });
        }
    }

    private double getRate(Participant participant) {
        long count = participant.homework().values().stream()
                .filter(v -> v == true)
                .count();
        return (double) (count * 100 / this.totalNumberOfEvents);
    }

    private String getMarkdownForParticipant(Participant participant) {
        return String.format("| %s %s | %.2f%% |\n", participant.username(),
                checkMark(participant, this.totalNumberOfEvents),
                getRate(participant));
    }
}


리팩토링 10. 함수를 명령으로 바꾸기 (Replace Function with Command)


예제

public class StudyDashboard {
        .
        .
        .
    public static void main(String[] args) throws IOException, InterruptedException {
        StudyDashboard studyDashboard = new StudyDashboard(15);
        studyDashboard.print();
    }

    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(participants.size()));

            participants.forEach(p -> {
                String markdownForHomework = getMarkdownForParticipant(p);
                writer.print(markdownForHomework);
            });
    }

    private Participant findParticipant(String username, List<Participant> participants) {
        .
        .
        .
    }

    private String getMarkdownForParticipant(Participant p) {
        .
        .
        .
    }

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

    private String checkMark(Participant p, int totalEvents) {
        .
        .
        .
    }
}


public class StudyDashboard {
        .
        .
        .
    public static void main(String[] args) throws IOException, InterruptedException {
        StudyDashboard studyDashboard = new StudyDashboard(15);
        studyDashboard.print();
    }

    private void print() throws IOException, InterruptedException {
        .
        .
        .
        // 변경된 부분
        new StudyPrinter(this.totalNumberOfEvents, participants).execute();
    }
    .
    .
    .
}
public class StudyPrinter {

    private int totalNumberOfEvents;

    private 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)) {
            this.participants.sort(Comparator.comparing(Participant::username));

            writer.print(header(this.participants.size()));

            this.participants.forEach(p -> {
                String markdownForHomework = getMarkdownForParticipant(p);
                writer.print(markdownForHomework);
            });
        }
    }

    private String getMarkdownForParticipant(Participant p) {
        .
        .
        .
    }

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

    private String checkMark(Participant p, int totalEvents) {
        .
        .
        .
    }
}


리팩토링 11. 조건문 분해하기 (Decompose Conditional)


예제

public class StudyDashboard {
        .
        .
        .
    private Participant findParticipant(String username, List<Participant> participants) {
        Participant participant = null;
        if (participants.stream().noneMatch(p -> p.username().equals(username))) {
            Participant participant;
            participant = new Participant(username);
            participants.add(participant);
        } else {
            participants.stream().filter(p -> p.username().equals(username)).findFirst().orElseThrow();
        }
        return participant;
    }
}



리팩토링 12. 반복문 쪼개기 (Split Loop)


예제

public class StudyDashboard {
        .
        .
        .
    private void print() throws IOException, InterruptedException {
        .
        .
        .
                        Date firstCreatedAt = null;
                        Participant first = null;

                        for (GHIssueComment comment : comments) {
                            // 성능 Bottleneck (Github API를 사용)
                            Participant participant = findParticipant(comment.getUserName(), participants);
                            // 반복문 안에서 두 가지 작업을 동시에 한다.
                            participant.setHomeworkDone(eventId);

                            if (firstCreatedAt == null || comment.getCreatedAt().before(firstCreatedAt)) {
                                firstCreatedAt = comment.getCreatedAt();
                                first = participant;
                            }
                        }

                        firstParticipantsForEachEvent[eventId - 1] = first;
        .
        .
        .
        }
}
public class StudyDashboard {
        .
        .
        .
    private void print() throws IOException, InterruptedException {
        .
        .
        .
                        checkHomework(comments, eventId);
                        firstParticipantsForEachEvent[eventId - 1] = findFirst(comments);
        .
        .
        .
        }

    private Participant findFirst(List<GHIssueComment> comments) throws IOException {
        Date firstCreatedAt = null;
        Participant first = null;
        for (GHIssueComment comment : comments) {
            Participant participant = findParticipant(comment.getUserName(), participants);

            if (firstCreatedAt == null || comment.getCreatedAt().before(firstCreatedAt)) {
                firstCreatedAt = comment.getCreatedAt();
                first = participant;
            }
        }
        return first;
    }

    private void checkHomework(List<GHIssueComment> comments, int eventId) {
        for (GHIssueComment comment : comments) {
            participant.setHomeworkDone(eventId);
        }
    }
}


리팩토링 13. 조건문을 다형성으로 바꾸기 (Replace Conditional with Polymorphism)


예시

public class StudyPrinter {

    private int totalNumberOfEvents;

    private List<Participant> participants;

    private PrinterMode printerMode;

    public StudyPrinter(int totalNumberOfEvents, List<Participant> participants, PrinterMode printerMode) {
        this.totalNumberOfEvents = totalNumberOfEvents;
        this.participants = participants;
        this.participants.sort(Comparator.comparing(Participant::username));
        this.printerMode = printerMode;
    }

    public void execute() throws IOException {
        switch (printerMode) {
            case CVS -> {
                try (FileWriter fileWriter = new FileWriter("participants.cvs");
                     PrintWriter writer = new PrintWriter(fileWriter)) {
                    writer.println(cvsHeader(this.participants.size()));
                    this.participants.forEach(p -> {
                        writer.println(getCvsForParticipant(p));
                    });
                }
            }
            case CONSOLE -> {
                this.participants.forEach(p -> {
                    System.out.printf("%s %s:%s\n", p.username(), checkMark(p), p.getRate(this.totalNumberOfEvents));
                });
            }
            case MARKDOWN -> {
                try (FileWriter fileWriter = new FileWriter("participants.md");
                     PrintWriter writer = new PrintWriter(fileWriter)) {

                    writer.print(header(this.participants.size()));

                    this.participants.forEach(p -> {
                        String markdownForHomework = getMarkdownForParticipant(p);
                        writer.print(markdownForHomework);
                    });
                }
            }
        }
    }
    .
    .
    .
}


public class ConsolePrinter extends StudyPrinter {

    public ConsolePrinter(int totalNumberOfEvents, List<Participant> participants) {
        super(totalNumberOfEvents, participants);
    }

    @Override
    public void execute() throws IOException {
        this.participants.forEach(p -> {
            System.out.printf("%s %s:%s\n", p.username(), checkMark(p), p.getRate(this.totalNumberOfEvents));
        });
    }
}
public class CvsPrinter extends StudyPrinter {

    public CvsPrinter(int totalNumberOfEvents, List<Participant> participants) {
        super(totalNumberOfEvents, participants);
    }

    @Override
    public void execute() throws IOException {
        try (FileWriter fileWriter = new FileWriter("participants.cvs");
             PrintWriter writer = new PrintWriter(fileWriter)) {
            writer.println(cvsHeader(this.participants.size()));
            this.participants.forEach(p -> {
                writer.println(getCvsForParticipant(p));
            });
        }
    }
    .
    .
    .
}
public class MarkdownPrinter extends StudyPrinter {

    public MarkdownPrinter(int totalNumberOfEvents, List<Participant> participants) {
        super(totalNumberOfEvents, participants);
    }

    @Override
    public void execute() throws IOException {
        try (FileWriter fileWriter = new FileWriter("participants.md");
             PrintWriter writer = new PrintWriter(fileWriter)) {

            writer.print(header(this.participants.size()));

            this.participants.forEach(p -> {
                String markdownForHomework = getMarkdownForParticipant(p);
                writer.print(markdownForHomework);
            });
        }
    }
    .
    .
    .
}
public abstract class StudyPrinter {

    protected int totalNumberOfEvents;
    protected List<Participant> participants;

    public StudyPrinter(int totalNumberOfEvents, List<Participant> participants) {
        this.totalNumberOfEvents = totalNumberOfEvents;
        this.participants = participants;
        this.participants.sort(Comparator.comparing(Participant::username));
    }

    public abstract void execute() throws IOException;

    protected String checkMark(Participant p) {
        StringBuilder line = new StringBuilder();
        for (int i = 1 ; i <= this.totalNumberOfEvents ; i++) {
            if(p.homework().containsKey(i) && p.homework().get(i)) {
                line.append("|:white_check_mark:");
            } else {
                line.append("|:x:");
            }
        }
        return line.toString();
    }
}