| 인프런 - 백기선님의 코딩으로 학습하는 리팩토링 강의를 수강하며 정리한 글입니다.
긴 함수
- 코드를 읽으면서 이해하기 난해하거나, 구현을 이해한다고 생각되면 긴 함수이다.
- 과거에는 작은 함수를 사용하는 경우 더 많은 서브 루틴 호출로 인한 오버헤드가 존재했다.
- 최근 사용하는 프로그래밍 언어는 최적화가 이루어지기 때문에 고려할 만한 오버헤드가 아니다.
- 최근 사용하는 프로그래밍 언어는 최적화가 이루어지기 때문에 고려할 만한 오버헤드가 아니다.
- 단점
- 코드를 이해하기 어렵다.
- 코드를 이해하기 어렵다.
- 방법
- 99% 함수 추출하기로 해결할 수 있다.
- 함수로 분리하면서 해당 함수로 전달해야 할 매개변수가 많아진다면 고려해 볼 리팩토링
- 임시 변수를 질의 함수로 바꾸기
- 매개변수 객체 만들기
- 객체 통째로 넘기기
- 조건문 분해하기
- 같은 조건의 여러 개의 Switch 문 -> 조건문을 다형성으로 바꾸기 적용 가능
- 반복문 안에서 여러 작업을 하고 있어 하나의 함수로 추출하기 어렵다면, 반복문 쪼개기 사용 가능
Refactoring 1. 임시 변수를 질의 함수로 바꾸기
- 변수 사용
- 반복해서 동일한 계산을 안 해도 된다.
- 이름을 사용해서 의미를 표현할 수 있다.
- 임시 변수를 함수로 추출하여 분리한다면 추출한 함수로 전달해야 할 매개변수가 줄어든다.
Before
public static void main(String[] args) {
long count = p.homework().values().stream()
.filter(v -> v == true)
.count();
double rate = count * 100 / totalNumberOfEvents;
String markdownForHomework = getMarkdownForHomework(totalNumberOfEvents, p, rate);
}
private String getMarkdownForHomework(int totalNumberOfEvents, Participant p, double rate) {
return String.format("| %s %s | %.2f%% |\n", p.username(), checkMark(p, totalNumberOfEvents), rate);
}
After
public static void main(String[] args) {
String markdownForHomework = getMarkdownForHomework(totalNumberOfEvents, p);
}
private static 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 getMarkdownForHomework(int totalNumberOfEvents, Participant p) {
return String.format("| %s %s | %.2f%% |\n", p.username(), checkMark(p, totalNumberOfEvents), getRate(totalNumberOfEvents, p));
}
Refactoring 2. 매개변수 객체 만들기(Introduce Parameter Object)
- 같은 매개변수들이 여러 메서드에서 사용된다면 해당 매개변수들을 묶은 자료 구조를 만들 수 있다.
- 만들어진 자료구조
- 해당 데이터간의 관계를 보다 명시적으로 나타낼 수 있다.
- 함수에 전달할 매개변수 개수를 줄일 수 있다.
- 도메인을 이해하는데 중요한 클래스로 발전할 수 있다.
Before
public static void main(String[] args) {
String markdownForHomework = getMarkdownForHomework(totalNumberOfEvents, p);
}
private static 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 getMarkdownForHomework(int totalNumberOfEvents, Participant p) {
return String.format("| %s %s | %.2f%% |\n", p.username(), checkMark(p, totalNumberOfEvents), getRate(totalNumberOfEvents, p));
}
After
public static void main(String[] args) {
String markdownForHomework = getMarkdownForHomework(totalNumberOfEvents, p);
}
private static double getRate(ParticipantPrinter participantPrinter) {
long count = participantPrinter.p().homework().values().stream()
.filter(v -> v == true)
.count();
double rate = count * 100 / participantPrinter.totalNumberOfEvents();
return rate;
}
private String getMarkdownForHomework(ParticipantPrinter participantPrinter) {
return String.format("| %s %s | %.2f%% |\n", participantPrinter.p().username(), checkMark(participantPrinter.p(), participantPrinter.totalNumberOfEvents()), getRate(participantPrinter));
}
public record ParticipantPrinter(int totalNumberOfEvents, Participant p) {
}
Refactoring 3. 객체 통째로 넘기기
- 여러 개의 값을 함수에 전달할 경우, 레코드 하나로 교체할 수 있다.
- 매개변수 목록을 줄일 수 있다.
- 향 후 추가되는 매개변수를 레코드에 추가하면 된다.
- 적용하기 전 의존성을 고려해야 한다.
- 해당 메서드의 위치가 적절하지 않을 가능성이 존재한다.
Refactoring 4. 함수를 명령으로 바꾸기
- 함수를 독립적인 객체인, Command로 만들어 사용 가능
- 커맨드 패턴 적용 시 장점
- 부가 기능으로 undo 기능을 만들 수 있다.
- 더 복잡한 기능을 구현하는데 필요한 여러 메서드 추가 가능
- 상속이나 템플릿을 활용할 수 있다.
- 복잡한 메서드를 여러 메서드나 필드를 활용해 쪼갤 수 있다.
- 단점
- 복잡도 증가
- 복잡도 증가
- 대부분의 경우 커맨드보단 함수를 사용하지만 커맨드 말고 다른 방법이 없을 경우에만 사용
Refactoring 5. 조건문 분해하기
- 여러 조건에 따라 달라지는 코드를 작성하면 긴 함수가 작성된다.
- 조건과 액션 모두 의도를 표현해야 한다.
- 기술적으로는 함수 추출하기와 동일한 리팩토링이지만 의도만 다르다.
Before
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;
}
After
private Participant findParticipant(String username, List<Participant> participants) {
Participant participant = null;
if (isNewParticipant(username, participants)) {
participant = createNewParticipant(username, participants);
} else {
participant = findExistingParticipant(username, participants);
}
return participant;
}
private static Participant createNewParticipant(String username, List<Participant> participants) {
Participant participant = new Participant(username);
participants.add(participant);
return participant;
}
private static Participant findExistingParticipant(String username, List<Participant> participants) {
return participants.stream().filter(p -> p.username().equals(username)).findFirst().orElseThrow();
}
private static boolean isNewParticipant(String username, List<Participant> participants) {
return participants.stream().noneMatch(p -> p.username().equals(username));
}
Refactoring 6. 반복문 쪼개기
- 반복문을 여러 개로 분리하면 코드를 보다 쉽게 이해하고 수정할 수 있다.
- 성능 문제를 야기할 수 있지만, 리팩토링과 성능 최적화는 별개의 작업으로 리팩토링 후 성능 최적화를 시도할 수 있다.
Before
public static void main(String[] args) {
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;
}
}
}
After
public static void main(String[] args) {
checkHomework(comments, eventId);
Participant first = findFirst(comments);
}
private void checkHomework(List<GHIssueComment> comments, int eventId) {
for (GHIssueComment comment : comments) {
Participant participant = findParticipant(comment.getUserName(), participants);
participant.setHomeworkDone(eventId);
}
}
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;
}
Refactoring 7. 조건문을 다형성으로 바꾸기
- 여러 타입에 따라 각기 다른 로직으로 처리해야 할 경우 다형성을 적용하여 조건문을 보다 명확하게 분리할 수 있다.
- 반복되는 조건문을 각기 다른 클래스를 만들어 제거할 수 있다.
- 공통으로 사용되는 로직은 상위 클래스에 두고 달라지는 부분만 하위 클래스에 둠으로써, 달라지는 부분만 강조할 수 있다.
- 모든 조건문을 다형성을 바꿔야 하는 것은 아니다. -> 정말 복잡하고 달라지는 부분이 있을 경우에만 적용하는 것이 좋다.
Before
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);
});
}
}
}
}
After
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);
});
}
}
}
'클린코드' 카테고리의 다른 글
[Refactoring] 6. 가변 데이터 (0) | 2022.09.09 |
---|---|
[Refactoring] 5. 전역 데이터 (0) | 2022.09.08 |
[Refactoring] 4. 긴 매개변수 목록 (0) | 2022.09.07 |
[Refactoring] 2. 중복 코드 (0) | 2022.09.05 |
[Refactoring] 1. 이해하기 힘든 이름 (0) | 2022.09.05 |