1. 아래 코드와 설명을 보고, [섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 읽기 좋은 코드로 리팩토링해 봅시다.
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;
}
2. SOLID에 대하여 자기만의 언어로 정리해 봅시다.
[자기만의 언어] 여러 블로그에 있는 내용, 다른 사람이 작성한 내용을 볼 수도 있지만, 결국 모든 학습의 종착지는 '자신의 언어로 표현할 수 있는가' 입니다. 저도 강의를 준비하고 촬영하면서 저만의 언어로 이해하고 곱씹은 내용을 여러분께 설명하고 있는 중이니까요 :)
1) 아래 코드와 설명을 보고, [섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 읽기 좋은 코드로 리팩토링해 봅시다.
AS-IS
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. Early Return
- 빠른 반환
- 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;
}
2. Reduce Thinking Depth
- 사고의 깊이 줄이기
public boolean validateOrder(Order order) {
if (order.getItems().size() == 0) {
log.info("주문 항목이 없습니다.");
return false;
}
if (!(order.getTotalPrice() > 0)) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
if (!order.hasCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}
3. Add Space(New Line) To Separate
- 관심사 단락마다 줄바꿈 추가
public boolean validateOrder(Order order) {
if (order.getItems().size() == 0) {
log.info("주문 항목이 없습니다.");
return false;
}
if (!(order.getTotalPrice() > 0)) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
if (!order.hasCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}
4. Refactor Negative Phrase(Best Effort For Positive)
- 부정어구를 최대한 긍정어구로 바꾸기
public boolean validateOrder(Order order) {
if (order.getItems().size() == 0) {
log.info("주문 항목이 없습니다.");
return false;
}
if (order.getTotalPrice() <= 0) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
if (order.isEmptyCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}
5. Handle Exceptions More Carefully
- 예외가 발생할 가능성 낮추기
- 외부 세계와의 접점이 어떤 값의 검증이 필요한 부분
- 의도한 예외와 의도하지 않은 예외 구분하기
보통 validateXXX 메서드는 true/false인 boolean값을 반환하거나, 명확한 상태를 담은 ValidateResultStatus같은 디테일한 객체를 만들던가 해서 처리한다고 생각한다
나는 그래서 그냥 간단하게 checkXXX 메서드로 바꿨다
public class OrderBusinessException extends RuntimeException {
public OrderBusinessException(String message) {
super(message);
}
public OrderBusinessException(String message, Throwable cause) {
super(message, cause);
}
}
public class NoItemException extends OrderBusinessException {
public NoItemException(String message) {
super(message);
}
}
public class IllegalPriceException extends OrderBusinessException {
public IllegalPriceException(String message) {
super(message);
}
}
public class EmptyCustomerInfoException extends OrderBusinessException {
public EmptyCustomerInfoException(String message) {
super(message);
}
}
public void checkOrder(Order order) {
if (order.getItems().size() == 0)
throw new NoItemException("주문 항목이 없습니다.");
if (order.getTotalPrice() <= 0)
throw new IllegalPriceException("올바르지 않은 총 가격입니다.");
if (order.isEmptyCustomerInfo())
throw new EmptyCustomerInfoException("사용자 정보가 없습니다.");
}
6. Tell, Don't Ask(TDA)
- 객체에게 요청하지 말고 가능한지 물어봐라
- 객체에 폭력적인 행위 가하지 말기, Getter/Setter는 최대한 방어적으로
편의상 static class로 퉁쳤다(실제로는 다른 public class로 있음)
좀 더 명확해진 메서드를 볼 수 있다
객체한테 물어보기
public class Order {
static class CustomerInfo {
public Optional<CustomerInfo> getCustomerInfo() {
return Optional.empty();
}
}
static class Item {
private int price;
public int getPrice() {
return price;
}
}
List<Item> items;
CustomerInfo customerInfo;
private int getTotalPrice() {
return items.stream()
.mapToInt(Item::getPrice)
.sum();
}
public boolean isEmptyItems() {
return this.items.isEmpty();
}
private boolean isPriceLessThanZero() {
return getTotalPrice() <= 0;
}
public boolean isEmptyCustomerInfo() {
return customerInfo.getCustomerInfo().isPresent();
}
}
public void checkOrder(Order order) {
if (order.isEmptyItems())
throw new NoItemException("주문 항목이 없습니다.");
if (order.isPriceLessThanZero())
throw new IllegalPriceException("올바르지 않은 총 가격입니다.");
if (order.isEmptyCustomerInfo())
throw new EmptyCustomerInfoException("사용자 정보가 없습니다.");
}
TO-BE
public void checkOrder(Order order) {
if (order.isEmptyItems())
throw new NoItemException("주문 항목이 없습니다.");
if (order.isPriceLessThanZero())
throw new IllegalPriceException("올바르지 않은 총 가격입니다.");
if (order.isEmptyCustomerInfo())
throw new EmptyCustomerInfoException("사용자 정보가 없습니다.");
}
2. SOLID에 대하여 자기만의 언어로 정리해 봅시다.
강의 마무리에서 우빈님이 하신 말씀에 내 생각을 추가해서 정리할 수 있을 것 같다
- SRP: Single Responsibility Principle
- 단일 책임 원칙
- 간단하지만 어렵다. 책임을 보는 눈을 계속 길러야 함
- 예시) 도메인 관련
- 주문 객체(클래스)는 주문 관리만 책임을 가짐
- 결제 객체(클래스)는 결제 처리만 책임을 가짐
- 배송 객체(클래스)는 배송 관련 로직만 책임을 가짐
- OCP: Open/Closed Principle
- Open-Closed 원칙
- 추상화를 통한) 확장에는 열려있고, 수정에는 닫혀있는 설계
- 예시) 레고 블록
- 기존 블록(코드)은 그대로 두고
- 새로운 블록(기능)을 추가할 수 있어야 함
- LSP: Liskov Substitution Principle
- 리스코프 치환 원칙
- 상속 구조에서 부모와 자식이 있을 때, 부모가 있는 곳에 자식클래스의 인스턴스가 끼어 들어가도 어색하지 않아야 한다(=오동작을 하지 않아야 한다)
- 예시) 자동차
- 자동차의 핸들을 조작하는 방법은 모든 자동차가 비슷해야 함
- BMW든 현대든, 운전자는 동일한 방식으로 운전할 수 있어야 함
- ISP: Interface Segregation Principle
- 인터페이스 분리 원칙
- 인터페이스를 기능 단위로 잘게 쪼개라(필요에 의해서)
- 예시) 스마트폰
- 전화 기능 인터페이스
- 카메라 기능 인터페이스
- 음악 재생 인터페이스
- DIP: Dependency Inversion Principle
- 추상화를 통한) 의존성 역전 원칙
- 고수준 모듈이 저수준 모듈을 직접적으로 참조하는 것이 아니라(그게 물론 자연스럽지만), 얘를 역전시켜서 각각 추상화를 의존하도록 만들어라
- 저수준 모델이 아무리 바뀌어도(구현체가 바뀌어도), 고수준 모델은 타격을 받지 않는다
- DI / IOC
- 예시) 추상화
- 콘센트(추상화)가 있기에 어떤 전자제품(구현체)이든 사용 가능
- 운전자는 자동차의 구체적인 브랜드가 아닌, '운전 가능한 차량'이라는 개념에 의존
결국 OOP 원칙이나 SOLID 원칙들은 인프콘2024에서 조영호님이 하신 말씀처럼 변화하는 비즈니스의 요구사항을 유연하게 처리하게 할 수 있는 가이드라인이라고 생각한다(애플리케이션 규모가 커지면 수정할 부분이 많아진다)
원칙들을 어느정도 지켜야하는건 맞지만, 가이드라인이기때문에 무조건적으로 지키려고 하기보다는 유연한 마인드를 가져야 한다고 생각한다
방금 한 말과 당연한 말이지만, 적절한 추상화를 해야된다. YAGNI 원칙이란 게 있는데, You Aren't Gonna Need It이라는 뜻이다
과도한 추상화는 오히려 복잡성을 높일 수 있기 때문에, 적당한 추상화가 필요하다
마지막으로 내 역량뿐만 아니라 팀원 전체의 역량, 프로젝트의 특성, 결정권자의 행동 전체 다 합의가 이루어져야 이런 설계를 코드에 녹일 수 있는 것이다!
'Course > 인프런 워밍업 클럽 스터디' 카테고리의 다른 글
1주차 회고 발자국 🐾 (6) | 2024.10.06 |
---|---|
Mission - Day 2 (1) | 2024.10.04 |
인프런 스터디(백엔드 클린 코드, 테스트 코드) OT (2) | 2024.09.28 |
댓글