📌 1. 코드 리팩토링
✔️ 사용자가 생성한 '주문'이 유효한지를 검증하는 메서드.
✔️ Order는 주문 객체이고, 필요하다면 Order에 추가적인 메서드를 만들어도 된다. (Order 내부의 구현을 구체적으로 할 필요는 없다.)
✔️ 필요하다면 메서드를 추출할 수 있다.
public boolean validateOrder(Order order) {
if (order.getItems().size() == 0) {
log.info("주문 항목이 없습니다.");
return false;
} else {
if (order.getTotalPrice() > 0) {
if (!order.hasCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
} else {
return true;
}
} else if (!(order.getTotalPrice() > 0)) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
}
return true;
}
1️⃣ 필요한 정보 빼내기
우선 위 코드를 보고 Order 가 가져야 하는 정보를 확인해보자.
- order.getItems().size() == 0
- order.getTotalPrice() > 0
- order.hasCustomerInfo()
- !(order.getTotalPrice() > 0)
코드로 Order에게 요청하는 것은 위와 같고, 이 코드가 요구하는 것으로 바꿔보자.
- 주문이 비어있는지?
- 주문 총 가격이 0원 이상인지?
- 사용자 정보가 존재하는지?
public boolean validateOrder(Order order) {
if (order.hasNoItems()) {
log.info("주문 항목이 없습니다.");
return false;
} else {
if (order.hasProperTotalPrice()) {
if (!order.hasCustomer()) {
log.info("사용자 정보가 없습니다.");
return false;
} else {
return true;
}
} else if (!(order.hasProperTotalPrice())) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
}
return true;
}
validateOrder 를 읽을 때, 조건들이 더 수월하게 읽힌다.
예를 들면, 주문 아이템들의 사이즈 = 0이면 이라는 말 보다는 추상화를 거친 주문 아이템이 없다면 이라는 말이 더 이해하기 쉬워진다.
2️⃣ ! 조건을 없애보자
! 조건은 생각을 한 번 꼬게 만든다. 먼저 조건을 이해한 뒤, !로 인해 반대되는 상황을 생각해야 한다. ! 조건을 없애보자.
public boolean validateOrder(Order order) {
if (order.hasNoItems()) {
log.info("주문 항목이 없습니다.");
return false;
} else {
if (order.hasProperTotalPrice()) {
if (order.hasNoCustomer()) {
log.info("사용자 정보가 없습니다.");
return false;
} else {
return true;
}
} else {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
}
}
3️⃣ 케이스를 확인해보자
위 코드를 보면 if-else로 인한 분기가 꽤 많다. 케이스를 분리해보자.
- 주문 아이템이 없는가?
- O ⇒ false
- X
- 올바른 총 가격을 가지고 있는가?
- O
- 사용자 정보가 없는가?
- O ⇒ false
- X ⇒ true
- 사용자 정보가 없는가?
- X ⇒ false
- O
- 올바른 총 가격을 가지고 있는가?
위 조건을 살펴보면, true가 반환되는 경우는 딱 한 가지이다. 즉, 유효한 주문의 도메인 규칙은 다음과 같다.
- 주문 아이템이 있어야 한다.
- 올바른 총 가격이 있어야 한다.
- 사용자 정보를 가지고 있어야 한다.
위 조건을 모두 가지고 있지 않으면 주문 유효성 검사에 실패한다. 위 3개를 만족하면 true를 반환하고 나머지 경우 false를 반환해도 되지만, 각 케이스에 맞게 로그를 기록해야 하므로 순서에 맞춰 검증이 필요하다. 따라서 없는 경우를 하나씩 제거하고, 이를 early return을 활용하여 더 읽기 좋게 리팩토링해보자.
✌️ 최종!
public boolean validateOrder(Order order) {
if (order.hasNoItems()) {
log.info("주문 항목이 없습니다.");
return false;
}
if (order.hasImproperTotalPrice()) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
if (order.hasNoCustomer()) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}
이전과 비교했을 때 각 케이스별로 이해하기 쉽고 읽기도 좋게 리팩토링되었다!
📌 2. 나만의 언어로 작성한 SOLID 원칙
SRP: Single Responsibility Principle
하나의 직업을 가져라.
내가 생각한 단일 책임이란, 현실 세계에 비유하자면 ‘하나의 직업을 가져라.’인 것 같다. 집에 TV가 고장나면 우리는 TV 수리 기사님에게 연락을 한다. TV가 고장났는데 인터넷 수리 기사님께 연락하진 않는다.
OCP: Open-Closed Principle
기능을 확장할 때는 그 기능만 작성해도 되도록 하라.
개인적으로 이해가 어려웠던 규칙이다. ‘열려있고 닫혀있다’는 의미가 쉽게 와닿지 않았다. 나는 이 문장을 ‘기능을 확장할 때는 그 기능만 작성해도 되도록 하라.’라고 이해했다.
LSP: Liskov Substitution Principle
부모 클래스의 행동 범위를 자식 클래스가 벗어나지 말아야한다.
말만 들으면 당연한 것처럼 여겨지지만, 이 문장에서 말하고자 하는 바는 ‘부모 클래스의 행동 범위를 자식 클래스가 벗어나지 말아야한다.’라고 생각한다.
ISP: Interface Segregation Principle
만능 인터페이스를 만들지마라.
이 원칙은 ‘만능 인터페이스를 만들지마라.’라고 이해했다. 모든걸 할 수 있는 만능 인터페이스를 만들지 말고, 책임에 맡게 나누고 분리하자!
DIP: Dependency Inversion Principle
인터페이스는 인터페이스에 맞게 사용해라.
이 원칙은 ‘인터페이스는 인터페이스에 맞게 사용해라.’라고 이해했다. 기껏 인터페이스로 선언하고 구현체를 알고있으면, 인터페이스로 굳이 선언한 이유가 없지 않을까라는 생각도 든다!
'인프런 워밍업 스터디' 카테고리의 다른 글
[4주차 발자국] 워밍업 스터디 마무리 (0) | 2025.04.04 |
---|---|
[3주차 발자국] 스프링과 테스트 코드 (0) | 2025.03.24 |
[인프런 워밍업 스터디] 2주차 발자국: Readable Code 적용기 (0) | 2025.03.17 |
[인프런 워밍업 스터디] 추상과 객체 지향의 구체화 (0) | 2025.03.09 |
AI 시대의 개발자의 가치 (0) | 2025.03.05 |