1. 알고자 하는 것
이전 Java 리팩토링 강의 내용 모두 유익하다고 생각했지만, 유독 이 내용은 간단해보이면서도 실무에서 정말 많이 적용 가능한 리팩토링 기법이라고 생각한다. 실제로 코드리뷰 때에도 해당 부분에 대해서 얘기를 많이 했고, 대체로 누군가 짠 코드를 처음 보는 입장이라고 생각해보았을 때, 조건문을 분해(or 추출)했을 때의 가독성이 정말 높아진다고 생각한다.
내가 짠 코드를 처음 보는 사람이 쉽게 이해할 수 있는 코드를 짜는 데에 좋은 첫 걸음이 될만한 부분이라고 생각한다.
항상 좋은 코드, 읽기 쉬운 코드를 짜도록 노력하는 습관을 들이자.
- 조건문 분해하기
2. 알게된 것
- 프로그램에는 언제나 조건이 있다.
- if-else로 분기되는 조건문은 단순한 조건인 경우에는 이해하기 쉽다.
- 또한, 조건문 내 액션이 단순한 경우에도 역시 가독성이 떨어지지 않는다.
// "조건"과 "액션" 모두 간결하고 짧다.
if (user.isVip()) {
vipCount++;
} else {
normalCount++;
}
- 하지만 다음과 같이 "조건"과 "액션"이 모두 복잡한 로직으로 구현되어 있다면?
- "조건"과 "액션" 모두 "의도"를 명확하게 표현하지 못한다.
- 즉, 가독성이 떨어지고 한 눈의 해당 메서드의 의도와 동작을 예상할 수 없다.
// if-else 문의 true, false가 어떤 조건에 의해 결정되는지 ?
// true일 때와 false일 때 액션이 무엇을 수행하는지 ?
private Participant findParticipant(String username, List<Participant> participants) {
Participant participant;
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;
}
- 이전에 살펴보았던 함수 추출(Extract Method)을 "조건"과 "액션"에 적용해 조건문 내에서 각각이 구현을 나타내는 것이 아닌 의미를 나타낼 수 있도록 한다.
- 이 때, 구현을 나타내던 부분을 하나의 의미를 갖는 메서드로 추출하므로 메서드명을 의미/의도를 잘 나타낼 수 있도록 결정하는 것이 중요하다.
private Participant findParticipant(String username, List<Participant> participants) {
Participant participant;
// 1. "조건" 메서드 추출 (새로운 participant인가?)
if (isNewParticipant(username, participants)) {
// 2. "액션" 메서드 추출 (새로운 participant라면 participant 객체 create)
participant = createNewParticipant(username, participants);
} else {
// 2. "액션" 메서드 추출 (기존 participant라면 participant 객체 find)
participant = findExistingParticipant(username, participants);
}
return participant;
}
// 메서드 추출
private Participant findExistingParticipant(String username, List<Participant> participants) {
return participants.stream().filter(p -> p.username().equals(username)).findFirst().orElseThrow();
}
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));
}
- 조건은 username에 해당하는 사용자가 participants 목록 내 존재하지 않는 새로운 사용자인지 여부에 따라 분기됨을 알 수 있다.
- 액션1(true)은 새로운 사용자이므로 participant 객체를 추가한다.
- 액션2(false)는 기존 사용자이므로 기존 participant 객체를 찾는다.
- 여러 줄로 복잡하게 구현되어진 조건과 액션이 3가지 의미를 띄도록 리팩토링 됨을 알 수 있다.
- 또한, 이렇게 메서드 추출로 조건문을 분해했을 때, 조건 ? 액션1 : 액션2와 같이 삼항연산자로 추가적인 리팩토링이 가능하도록 쉽게 유도할 수 있다.
private Participant findParticipant(String username, List<Participant> participants) {
// 새로운 사용자라면 새로운 participant 객체 생성 / 기존 사용자라면 기존 participant 객체 반환
return isNewParticipant(username, participants) ?
createNewParticipant(username, participants) :
findExistingParticipant(username, participants);
}
3. 정리
- 복잡한 조건문 분기는 그 depth가 깊어질수록 가독성이 떨어진다.
- "조건"과 분기에 따른 "액션" 로직이 복잡해 가독성이 떨어질 때, 함수 추출(Extract Method)을 활용해 적절한 메서드명으로 나타냄으로써 복잡한 구현이 아닌 "의미"를 표현하도록 조건문을 분해한다.
- 이렇게 복잡한 로직이 하나의 의미를 띄도록 조건문을 분해했다면, 조건 ? 액션1 : 액션2와 같은 삼항연산자 등의 추가적인 리팩토링의 가능성을 쉽게 파악할 수 있다.
Reference
'Clean Code' 카테고리의 다른 글
Java 리팩토링 - 플래그 인수 제거하기 (0) | 2023.11.12 |
---|---|
Java 리팩토링 - 조건문을 다형성으로 바꾸기 (0) | 2023.11.05 |
Java 리팩토링 - 함수를 명령(Command)으로 바꾸기 (0) | 2023.10.06 |
Java 리팩토링 - 메서드 추출 시 매개변수가 많아질 경우 [3. 객체 통째로 넘기기] (0) | 2023.09.26 |
Java 리팩토링 - 메서드 추출 시 매개변수가 많아질 경우 [2. 매개변수 객체 만들기] (0) | 2023.06.20 |