4일차 과제는 이 코드를 리팩토링하고
Solid에 대해서 설명하는 내용이다.
우선은 리팩토링부터해보자
리팩토링
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. if- else 제거
public boolean validateOrder(Order order) {
if (order.getItems().size() == 0) {
log.info("주문 항목이 없습니다.");
return false;
}
if (order.getTotalPrice() > 0) {
if (!order.hasCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}
if (!(order.getTotalPrice() > 0)) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
return true;
}
위 코드에서 if - else 문을 모두 if 문으로 변경해줬다.
이미 코드내에서 조건문에 해당하면 return으로 반환하면서 함수를 끝내기때문에 else로 분기문을 나눌필요가 없었다.
- 조건1 - 주문아이템이 0개이면 false
- 조건2 - 주문한상품금액이 0 이상인 경우
- 조건2-1 - 주문에 사용자정보가없는경우 false
- 조건2-2 - 사용자정보가있으면 true를 리턴
- 조건3 - 주문금액이 0원보다적은경우 false
조건을 정리하다보니 기본 리턴을 true로 잡고 각 예외상황에 대한 처리만 조건으로 해주면 될것같다.
부정연산자 제거
public boolean validateOrder(Order order) {
if (order.getItems().size() == 0) {
log.info("주문 항목이 없습니다.");
return false;
}
if (order.getTotalPrice() > 0) {
if (orderHasNotCustomerInfo(order)) {
log.info("사용자 정보가 없습니다.");
return false;
}
}
if (order.getTotalPrice() < 0) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
return true;
}
private boolean orderHasNotCustomerInfo(Order order) {
return !order.hasCustomerInfo();
}
! 연산자로 한번 뒤집어서 생각하게되던 연산자를 제거하고 메서드로 추출해서 코드의 의미를 좀 더 빠르고 명확하게 이해할수있게만들었다.
- !(order.getTotalPrice( ) > 0) 부분은 - totalPrice가 0이상이 아닌경우 인데 부등호를 뒤집어서 부정연산자를 빼주었다
- !order.hasCustomerInfo( ) 는 order가 고객정보를 가졌는지를 boolean으로 리턴하는 메서드인것같은데 우선은 여기서 메서드로 추출해서 부정을하는 메서드를 만들었다. 실제 부분에서는 해당 Order클래스에 고객정보가없을때 true를 리턴해주는 메서드를 만드는것도 좋을것같다.
메서드로 추출해서 추상화레벨 올리기
public boolean validateOrder(Order order) {
if (order.doesNotHaveItem()) {
log.info("주문 항목이 없습니다.");
return false;
}
if (totalPriceBiggerThanZero(order)) { //토탈주문금액이 0원이 아닌데 사용자정보가없는경우 - 정상주문인데 사용자 정보가없는경우
if (orderHasNotCustomerInfo(order)) {
log.info("사용자 정보가 없습니다.");
return false;
}
}
if (totalPriceLowerThanZero(order)) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
return true;
}
private static boolean totalPriceLowerThanZero(Order order) {
return order.getTotalPrice() < 0;
}
private static boolean totalPriceBiggerThanZero(Order order) {
return order.getTotalPrice() > 0;
}
private boolean orderHasNotCustomerInfo(Order order) {
return !order.hasCustomerInfo();
}
이번에는 조건문에들어가는 조건들의 추상화 레벨을 올려주었다.
함수의 이름만으로 어떤걸 검사해서 어떤 값을 리턴하는지 파악이 가능해졌다.
이제 중첩된 조건문을 수정하려고하는데 2가지 조건으로 검사한후 false를 리턴해주고있다
1. 토탈주문금액이 0원이 아닌경우
2. 주문한사용자의 정보가 없는경우
개인 적인 생각이지만 주문한 사용자의 정보가없는경우는 무조건 false가 나와야하지않을까 라는 생각이들었다.
case1 - 주문을 한 금액은 있는데 주문자 정보가없는경우
case2 - 주문을 한 금액은 있는데 주문자 정보가 있는경우 ok
- 주문한 금액이없는경우는 아래의 totalPriceLowerThanZero에서 걸러진다.
케이스를 봤을때 주문한금액으로 검증할 필요가 없을것같다 totalPriceLowerThanZero를 위로올리면 주문금액이 0원 이하인 케이스는 모두 걸러지고 아래에는 토탈주문금액이 0원이상인 케이스만 남아있게된다.
조건문의 순서를 변경해서 중첩된 조건문제거
public boolean validateOrder(Order order) {
if (order.doesNotHaveItem()) {
log.info("주문 항목이 없습니다.");
return false;
}
if (totalPriceLowerThanZero(order)) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
if (orderHasNotCustomerInfo(order)) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}
private static boolean totalPriceLowerThanZero(Order order) {
return order.getTotalPrice() < 0;
}
private static boolean totalPriceBiggerThanZero(Order order) {
return order.getTotalPrice() > 0;
}
private boolean orderHasNotCustomerInfo(Order order) {
return !order.hasCustomerInfo();
}
최종적으로 이렇게 리팩토링 해주었다.
각 조건문마다 검증하는 항목이 달라서 메소드로 추출할까 고민했는데.
메서드로 추출해도 각 항목에서 early return 으로 메서드를 종료하려면 조건문을 똑같이 3개써야 할것같아서.
현재는 이정도가 딱 좋다고 생각한다.
SOLID에 대한 정리
- SRP - single responsibility principle 단일 책임 원칙
- 하나의 클래스는 하나의 책임만 가진다.
- 클래스를 생성하고 기능을 정의하면서 클래스가 가진 역할이 여러개인경우 클래스를 분리한다.
- Money라는 클래스는 돈에대한 역할만 수행하고 돈을 저장하거나 계산하는 역할은 다른 클래스로 분리가가능하다. 돈을 저장하는건 지갑이나 , 창고같은 클래스에 맡기고 계산은 계산기가 한다.
- OCP - Open-Closed principle
- 확장에 열려있고 수정에는 닫혀있다.
- 기능을 쉽게 확장하고 적게 수정
- 인터페이스나 추상화를통해서 필수기능들을 정의하고 구현체로 기능을 구현 , 기능에대해 수정사항이나 각 상황별 다양한 구현이 필요하다면 그때에 각각 다른 구현체들로 변경하면서 기능을 확장할수있다.
- LSP - Liskov Substitution Principle
- 부모클래스의 인스턴스를 자식클래스의 인스턴스로 치환할수 있어야 한다.
- 부모클래스의 기능을 확장한 자식클래스가 들어가도 동일하게 동작해야한다.
- ISP - Interface Segregation Principle
- 사용하지않는 인터페이스에 의존하면 안된다.
- 만약 사용하지않는 인터페이스에 의존하게된다면 해당 인터페이스의 기능이 충분히 분리되지않았다. 더 작게 기능을 분리해서 필요한 인터페이스에만 의존하도록한다.
- DIP - Dependency Inversion Principle
- 상위수준모듈은 저수준모듈에 의존해서는 안된다. 둘다 추상화에 의존
- 저수준 모듈에 의존하게된다면 저수준 모듈이 수정될때 고수준 모듈도 같이 수정해야한다.
- 추상화에 의존하게된다면 구현부분인 저수준모듈이 수정되어도 고수준모듈은 수정되지않는다
'백엔드 > 자바' 카테고리의 다른 글
[인프런 워밍업 클럽_0기] BE과제 미니프로젝트 4단계 - 초과근무시간 계산 (0) | 2024.03.27 |
---|---|
[인프런 워밍업 클럽_0기] BE과제 미니프로젝트 3단계 - 직원의 근무시간을 조회하는기능 수정 (0) | 2024.03.19 |
[인프런 워밍업 클럽_0기] BE과제 미니프로젝트 3단계 - 연차신청 연차조회 (0) | 2024.03.15 |
스프링부트 예외처리 - @RestControllerAdvice (1) | 2024.03.15 |
[인프런 워밍업 클럽_0기] BE과제 미니프로젝트 2단계 - 출퇴근기록 조회 (0) | 2024.03.11 |