함수가 길 수록 더 이해하기 어려움
과거에 작은 함수를 사용하는 경우 더 많은 서브루틴 호출로 인한 오버헤드 발생
작은 함수에 좋은 이름을 사용한다면 함수의 코드를 보지 않고 이해 가능
어떤 코드에 주석을 남기고 싶으면 대신 함수를 만들고 함수의 이름으로 의도 표현
분기점에서 어떤 행위를 하는지 한 줄씩 읽어가며 전체 코드를 이해해야 한다
조건에 따라 분해하여 분기내용, true, false 3줄로 어떤 행위가 발생할지 내부코드를 보지 않고 파악이 가능하다
class Refactoring {
private Participant findParticipant(String username, List<Participant> participants) {
Participant participant = null;
if (participants.stream().noneMatch(p -> p.username().equals(username))) {
participant = new Participant(username);
participants.add(participant);
} else {
participant = participants.stream().filter(p -> p.username().equals(username))
.findFirst()
.orElseThrow();
}
return participant;
}
}
class Refactoring {
private Participant findParticipant(String username, List<Participant> participants) {
return isNewParticipant(username, participants) ?
createNewParticipant(username, participants) :
findExistingParticipant(username, participants);
}
private Participant findExistingParticipant(String username, List<Participant> participants) {
Participant participant;
participant = participants.stream().filter(p -> p.username().equals(username))
.findFirst()
.orElseThrow();
return participant;
}
private Participant createNewParticipant(String username, List<Participant> participants) {
Participant participant;
participant = new Participant(username);
participants.add(participant);
return participant;
}
private boolean isNewParticipant(String username, List<Participant> participants) {
return participants.stream().noneMatch(p -> p.username().equals(username));
}
}
반복문을 작업 단위로 쪼개는 경우 작업 개수만큼 반복문을 생성하여 작업 코드를 나누어 메서드를 생성한다
위 4번째에서 언급했듯이 성능 문제가 생길 수 있으나 리팩토링 후 성능 최적화 시도로 해결
class Refactoring {
private void print() throws IOException, InterruptedException {
GHRepository ghRepository = getGhRepository();
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 = ghRepository.getIssue(eventId);
List<GHIssueComment> comments = issue.getComments();
Date firstCreatedAt = null;
Participant first = null;
for (GHIssueComment comment : comments) {
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;
latch.countDown();
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
});
}
latch.await();
service.shutdown();
new StudyPrinter(this.totalNumberOfEvents, this.participants).execute();
printFirstParticipants();
}
}
class Refactoring {
private void print() throws IOException, InterruptedException {
GHRepository ghRepository = getGhRepository();
checkGithubIssues(ghRepository);
new StudyPrint(this.totalNumberOfEvents, this.participants).execute();
printFirstParticipants();
}
private void checkGithubIssues(GHRepository ghRepository) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(8);
CountDownLatch latch = new CountDownLatch(totalNumberOfEvents);
for (int index = 1 ; index <= totalNumberOfEvents ; index++) {
int eventId = index;
service.execute(() -> {
try {
GHIssue issue = ghRepository.getIssue(eventId);
List<GHIssueComment> comments = issue.getComments();
checkHomework(comments, eventId);
firstParticipantsForEachEvent[eventId - 1] = findFirst(comments);
latch.countDown();
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
});
}
latch.await();
service.shutdown();
}
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;
}
}
enum 타입별 하위 클래스를 생성하고 상위 클래스를 상속받아 execute 를
오버라이딩 하고 switch 문 작업을 타입별로 쪼갠다
이 후 불필요한 PrinterMode enum 클래스 제거
변경 후checkMark 메서드는 하위 클래스에 2곳에서 사용중이므로 부모 클래스에 위치하고
하위클래스에서 슈퍼클래스 메서드에 접근하는 구조로 중복을 제거
Use 클래스를 보면 StudyPrinter 클래스 생성자에 type 을 넘겨 생성하던 방식에서
타입을 제거하고 필요한 하위클래스를 직접 생성하는 구조로 변경
public enum PrinterMode {
CONSOLE, CVS, MARKDOWN
}
class Refactoring {
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);
});
}
}
}
}
}
class Use {
private void print() throws IOException, InterruptedException {
checkGithubIssues(getGhRepository());
new StudyPrinter(this.totalNumberOfEvents, this.participants, PrinterMode.CONSOLE).execute();
}
}
abstract class Refactoring {
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();
}
}
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 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 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);
});
}
}
// ... 오버라이딩한 메서드에서 필요한 메서드들 상위클래스에서 빼낸다
}
class Use {
private void print() throws IOException, InterruptedException {
checkGithubIssues(getGhRepository());
new MarkdownPrinter(this.totalNumberOfEvents, this.participants).execute();
}
}