본문 바로가기
320x100
320x100

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에 대하여 자기만의 언어 정리해 봅시다.

[자기만의 언어] 여러 블로그에 있는 내용, 다른 사람이 작성한 내용을 볼 수도 있지만, 결국 모든 학습의 종착지는 '자신의 언어로 표현할 수 있는가' 입니다. 저도 강의를 준비하고 촬영하면서 저만의 언어로 이해하고 곱씹은 내용을 여러분께 설명하고 있는 중이니까요 :)

 


 

섹션3 강의수강

 

섹션3 키워드 정리

 

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 사용 지양하기

Early Return

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

  • 사고의 깊이 줄이기

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

  • 관심사 단락마다 줄바꿈 추가

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)

  • 부정어구를 최대한 긍정어구로 바꾸기

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

  • 예외가 발생할 가능성 낮추기
  • 외부 세계와의 접점이 어떤 값의 검증이 필요한 부분
  • 의도한 예외와 의도하지 않은 예외 구분하기

비즈니스에 사용할 언체크드(런타임) 예외 만들기
valid -> check

보통 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에 대하여 자기만의 언어 정리해 봅시다.

섹션4 강의수강
섹션4 키워드 정리

강의 마무리에서 우빈님이 하신 말씀에 내 생각을 추가해서 정리할 수 있을 것 같다

  • 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이라는 뜻이다

과도한 추상화는 오히려 복잡성을 높일 수 있기 때문에, 적당한 추상화가 필요하다

 

마지막으로 내 역량뿐만 아니라 팀원 전체의 역량, 프로젝트의 특성, 결정권자의 행동 전체 다 합의가 이루어져야 이런 설계를 코드에 녹일 수 있는 것이다!

320x100

댓글