⌨️ 인프런 워밍업 클럽 2기 미션 Day-4
인프런 박우빈님의 백엔드 과정 진행 중 미션을 작성해야 하는데 마땅한 곳이 없어 끄적끄적인다.
1. 리팩토링
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;
}
첫 번째는 위의 코드를 리팩토링 하는 것인데, 어떤 부분을 고려했고 내 생각은 어땠는 지 좀 자유롭게 작성하려 한다.
✅ 미션 내용은 섹션 3을 참고하는 것이나.. 너무 불편한데..?
어떤 점이 불편했냐면, 개인적으로는 Order를 Vaildate하는 것이 바깥에 있다.
개인적으로는 Order가 해당 책임을 가지고 있었으면 어땠을까? 하는 바람이나
섹션3에서는 다루는 부분이 아니라 일단은 최대한 덜어내려 했다.
🤔 처음으로는 빠른 리턴
리턴을 빨리 해주므로 이후의 코드까지 쭉 흐름을 이어가서 결론을 내는 것이 아니라 빠르게 사고를 정리할 수 있도록
고민해봤다.
🤔 사용자 정보를 확인하는 게 꼭 Price 안에 있어야 할까?
생각해보니 가격이 문제가 없으면 사용자 정보를 확인하는 로직이 조금 어색하게 느껴졌다.
이게 꼭 여기에 중첩되어야 할까? 나는 바깥으로 빼내는 것이 더욱 적당하다고 생각했다.
🤔 부정문을 조금 직관적으로
읽어가는 흐름 그대로 부정문이 동작하도록 고민해봤다.
밋 이름 또한 그거에 맞게 변형해봤다.
🤔 마지막으로는 메서드 이름과 흐름을 변경
이게 Order 객체가 검증을 하는 것이 아니라 바깥에서 물어보니까.. 좀 더 다른 이름으로 변경하고 싶었다.
또한, 개인적으로는 사용자 정보를 확인하는게 가장 상단에 들어가는 것이 좋겠다는 생각을 했다.
✅ 중요하게 고려한 점
미션 내용에 충실
미션 내용에 최대한 충실하게 구현하려 했다. 뭔가 더 발전시킬 순 있을 것 같지만
요구 사항 자체가 섹션3을 중심으로이기 때문에 최대한 그것에 맞게 시도했다
✅ 결과
public class Main {
public static void main(String[] args) {
Order order = new Order(List.of("자동차"), BigDecimal.ONE, "TEST");
boolean result = isValidatedOrder(order);
}
// 검증된 주문이니? 하고 Order 질문 Orders는 그렇다/아니다를 return
public static boolean isValidatedOrder(Order order) {
return order.isValidOrder();
}
}
public class Order {
private static final Logger log = LoggerFactory.getLogger(Order.class);
private final List<String> items;
private final BigDecimal totalPrice;
private final String customerInfo;
public Order(List<String> items, BigDecimal totalPrice, String customerInfo) {
this.items = items;
this.totalPrice = totalPrice;
this.customerInfo = customerInfo;
}
public boolean isValidOrder() {
if (isCustomerNotExist()) return false;
if (itemListIsEmpty()) return false;
if (isIncorrectPrice()) return false;
return true;
}
private boolean itemListIsEmpty() {
return validateOutFormatter(items.isEmpty(),"주문 항목이 없습니다.");
}
private boolean isIncorrectPrice() {
return validateOutFormatter(totalPrice.compareTo(BigDecimal.ZERO) <= 0, "올바르지 않은 총 가격입니다.");
}
private boolean isCustomerNotExist() {
return validateOutFormatter(customerInfo == null, "사용자 정보가 없습니다.");
}
private boolean validateOutFormatter(boolean condition, String errorMessage) {
if (condition) {
log.info(errorMessage);
return true;
}
return false;
}
}
❓정리
생각보다 더 복잡해진거 같지만.. 잘 모르겠다 다른 분들의 작업물도 좀 보고
이제 연습하는 단계니 점점 발전하면 좋을 것 같다는 생각을 한다.
코드라는게 생각보다 반복적으로 짤 일이 없다. (단기간에 반복숙달의 관점에서)
그래서 한번 하고 마는 경우가 많기에 매번 가물가물하다.
그러므로 최대한 억지로라도 반복하여 체화 시키는 것이 중요한 것 같다.
2. SOLID에 대해
두 번째 미션은 SOLID에 대해 나만의 언어로 정리하는 것이다.
나만의 언어라고 하면 억지로 비유를 하라는 게 아니라 그저 내가 이해한 것을 한번 정리해보란 뜻임을 먼저 밝힌다.
2.1 SRP
여러 강연들과 서적을 많이 보고 제일 많이 고민한 부분이다.
- 조영호님 - 객체지향은 여전히 유효한가? (2024 인프콘)
- 토비님 - 클린 (스프링) 코드 (2024 인프콘)
- 재민님 - 지속성장 가능한 소프트웨어를 만들어 가는 방법 (유튜브)
- 로버트.C.마틴 - 클린 아키텍쳐 (서적)
가장 크게는 위의 네 불릿을 베이스로 자질구레한 것도 많이 읽고 보고 했다.
내가 정리한 SRP는
한 클래스의 변경이 하나의 액터에 의해서만 일어나야 한다 이다.
그림과 같이 한 클래스가 두 개의 액터에 의해 변경된다면 SRP를 잘 지키기 못했다고 생각한다.
User의 입장에서는 Admin으로 인해 내가 사용하는 Some Class가 변경되었기 때문이다.
2.2 OCP
개방 폐쇄 원칙은
확장에는 열려 있고 수정에는 닫혀 있어야 한다는 내용인데
많은 예시들과 마찬가지로 나 역시도
새로운 기능을 추가 할 때 기존의 코드의 변경이 없어야 한다고 받아들였다.
실무에서 생각보다 잘 안지켜지는 부분이 아닐까 하는데,
처음부터 확장을 고려하고 만든 시스템이 아니라면, 필연적으로 새로운 기능을 추가하며 기존의 코드에 살짝의
변형이 있을 수도 있기 때문이다 (대부분은 아니겠지만)
애초에 그런 내용을 최소화 하기 위해 잘 추상화하고 인터페이스를 활용하는 것이 좋다.
2.3 LSP
이름부터 와닿지가 않는다 리스코프 치환 원칙 이거는 이름 부터 일단 바꿔야하지 않나 싶다.
그냥 이거는 위아래를 지키자는 말이다.
위아래가 제대로 안지켜진다면 상속된 클래스를 사용할 때 좀 골치아프다.. 대부분의 라이브러리는 아니지만
개인이 잘 모르고 상속한 클래스를 활용한다면 이런 문제들이 종종 발생한다.
그냥 자바 기초적으로 생각해보자
상속된 자식클래스는 당연히 부모클래스를 대체할 수 있다. 부모의 기능을 전부 포함하고 있기 때문이다.
또한 자식 클래스는 부모를 제어 해서는 안된다 일단 오륜에 반하고 유교 국가에선 말이 안되는 일이다.
농이고,
부모의 자식이 몇인지 모른다 하나의 자식이 부모를 바꿔 버린다면 다른 자식들이 기분 상하는 일이 발생할 것이다.
2.4 ISP
인터페이스 분리 원칙인데 이거는 심플하다
그냥 쓰는 거만 선언된 인터페이스를 상속해라.
그냥 해보면 안다 이거는.. 한번이라도 인터페이스를 통해 추상화를 시도해봤다면
한 인터페이스에 이거저거 다 들어있으면 상속할 때 굉장히 피곤한 것을 알 것이다.
여기서 SRP랑도 연계된다.
나는 이걸 실천할 때는 그냥 먹을 만큼만 담기라는 마음으로 구현한다.
2.5 DIP
의존성 역전 원칙인데, 많이들 착각하는 부분이 스프링의 IoC와 DI를 이거랑 엮어서 생각한다.
의존성 역전은 쉽게 말해 고수준의 모듈을 생성할 때 저수준의 모듈을 참조해서 만드는 것이 아닌 둘다 다
모두 추상화된 객체에 의존하는 것이다.
DIP그 자체는 스프링에 의존 하지 않고도 개발자가 스스로 할 수 있다. 근데 이걸 하나하나 다 뒤집자니 개발자 속도 뒤집어지니까
스프링에서 자동적으로 할 수 있게 해준 것이다.
아무튼 이건 의존성이 정방향이면 뭐가 됐든 내가 객체를 참조한다면 고수준의 모듈도 저수준의 모듈의 변경 사항을 전파 받기 때문에 뒤집는 거다.
❓ 정리
개인적으로 SRP 빼고는 하다보면 다 불편함을 느껴서 알게 모르게 지키게 된다.
말 그대로 지켜지는 것이라 해야하나.. 실질적으로 타격이 있다.
근데 SRP는 잘 모르곘다 좀 어렵다.
중독된 거 마냥 당장에는 불편함이 없으나 시간이 지나고 이걸 해결하려고 보면 답이없다..
아무튼 스스로 SOLID에 대해 정리해봤는데, 크게 다를건 없는거 같다..
크게 다르면 근데 애초에 원칙이 아니지 않나 싶다..?
그래서 나는 이걸 나만의 언어가 아니라 나의 실천방식으로 작성해봤는데.. 어찌됐든 했다는 게 중요한 게 아닐까..?
🌟REFERENCE
인프런 : 박우빈 - 읽기 좋은 코드를 작성하는 사고법
인프콘 2024
유튜브 : 제미니의 개발 실무